LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - chordlayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 90.0 % 2006 1805
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 <cfloat>
      23              : 
      24              : #include "chordlayout.h"
      25              : 
      26              : #include "containers.h"
      27              : 
      28              : #include "dom/accidental.h"
      29              : #include "dom/arpeggio.h"
      30              : #include "dom/chordbracket.h"
      31              : #include "dom/beam.h"
      32              : #include "dom/chord.h"
      33              : #include "dom/factory.h"
      34              : #include "dom/fingering.h"
      35              : #include "dom/glissando.h"
      36              : #include "dom/guitarbend.h"
      37              : #include "dom/hook.h"
      38              : #include "dom/ledgerline.h"
      39              : #include "dom/lyrics.h"
      40              : #include "dom/measure.h"
      41              : #include "dom/note.h"
      42              : #include "dom/ornament.h"
      43              : #include "dom/page.h"
      44              : #include "dom/parenthesis.h"
      45              : #include "dom/part.h"
      46              : #include "dom/rest.h"
      47              : #include "dom/score.h"
      48              : #include "dom/segment.h"
      49              : #include "dom/slur.h"
      50              : #include "dom/staff.h"
      51              : #include "dom/stem.h"
      52              : #include "dom/stemslash.h"
      53              : #include "dom/system.h"
      54              : #include "dom/tie.h"
      55              : #include "dom/tremolosinglechord.h"
      56              : #include "dom/tremolotwochord.h"
      57              : #include "dom/utils.h"
      58              : #include "editing/editchord.h"
      59              : #include "editing/navigation.h"
      60              : 
      61              : #include "accidentalslayout.h"
      62              : #include "arpeggiolayout.h"
      63              : #include "autoplace.h"
      64              : #include "beamlayout.h"
      65              : #include "beamtremololayout.h"
      66              : #include "horizontalspacing.h"
      67              : #include "parenthesislayout.h"
      68              : #include "restlayout.h"
      69              : #include "slurtielayout.h"
      70              : #include "stemlayout.h"
      71              : #include "systemlayout.h"
      72              : #include "tlayout.h"
      73              : #include "tremololayout.h"
      74              : 
      75              : using namespace muse;
      76              : using namespace mu::engraving;
      77              : using namespace mu::engraving::rendering::score;
      78              : 
      79              : static constexpr Spatium STAFFTYPE_TAB_DEFAULTDOTDIST_X = 0.75_sp;
      80              : 
      81        49756 : void ChordLayout::layout(Chord* item, LayoutContext& ctx)
      82              : {
      83        49756 :     if (item->notes().empty()) {
      84            0 :         return;
      85              :     }
      86        49756 :     int gi = 0;
      87        50677 :     for (Chord* c : item->graceNotes()) {
      88              :         // HACK: graceIndex is not well-maintained on add & remove
      89              :         // so rebuild now
      90          921 :         c->setGraceIndex(gi++);
      91              :     }
      92              : 
      93        49756 :     if (item->onTabStaff()) {
      94          798 :         layoutTablature(item, ctx);
      95              :     } else {
      96        48958 :         layoutPitched(item, ctx);
      97              :     }
      98              : }
      99              : 
     100        49847 : void ChordLayout::layoutPitched(Chord* item, LayoutContext& ctx)
     101              : {
     102        50736 :     for (Chord* c : item->graceNotes()) {
     103          889 :         layoutPitched(c, ctx);
     104              :     }
     105              : 
     106        49847 :     double mag_             = item->staff() ? item->staff()->staffMag(item) : 1.0;      // palette elements do not have a staff
     107        49847 :     double dotNoteDistance  = ctx.conf().styleAbsolute(Sid::dotNoteDistance) * mag_;
     108              : 
     109        49847 :     double chordX           = (item->noteType() == NoteType::NORMAL) ? item->ldata()->pos().x() : 0.0;
     110              : 
     111        49847 :     double lll    = 0.0;           // space to leave at left of chord
     112        49847 :     double rrr    = 0.0;           // space to leave at right of chord
     113        49847 :     double lhead  = 0.0;           // amount of notehead to left of chord origin
     114        49847 :     Note* upnote = item->upNote();
     115        49847 :     Note* downnote = item->downNote();
     116        49847 :     Note* leftNote = nullptr;
     117              : 
     118        49847 :     delete item->tabDur();     // no TAB? no duration symbol! (may happen when converting a TAB into PITCHED)
     119        49847 :     item->setTabDur(nullptr);
     120              : 
     121              :     //-----------------------------------------
     122              :     //  process notes
     123              :     //-----------------------------------------
     124              : 
     125              :     // Keeps track if there are any accidentals in this chord.
     126              :     // Used to remove excess space in front of arpeggios.
     127              :     // See GitHub issue #8970 for more details.
     128              :     // https://github.com/musescore/MuseScore/issues/8970
     129        49847 :     std::vector<Accidental*> chordAccidentals;
     130              : 
     131       108545 :     for (Note* note : item->notes()) {
     132        58698 :         TLayout::layoutNote(note, note->mutldata());
     133        58698 :         double x1 = note->pos().x() + chordX;
     134        58698 :         double x2 = x1 + note->headWidth();
     135        58698 :         lll      = std::max(lll, -x1);
     136        58698 :         rrr      = std::max(rrr, x2);
     137              :         // track amount of space due to notehead only
     138        58698 :         lhead    = std::max(lhead, -x1);
     139        58698 :         if (!leftNote || note->x() < leftNote->x()) {
     140        49899 :             leftNote = note;
     141              :         }
     142              : 
     143        58698 :         Accidental* accidental = note->accidental();
     144        58698 :         if (accidental && accidental->visible()) {
     145         4007 :             chordAccidentals.push_back(accidental);
     146              :         }
     147        58698 :         if (accidental && accidental->addToSkyline() && !note->fixed()) {
     148              :             // convert x position of accidental to segment coordinate system
     149         4002 :             double x = accidental->pos().x() + note->pos().x() + chordX;
     150              :             // distance from accidental to note already taken into account
     151              :             // but here perhaps we create more padding in *front* of accidental?
     152         4002 :             x -= ctx.conf().styleAbsolute(Sid::accidentalDistance) * mag_;
     153         4002 :             lll = std::max(lll, -x);
     154              :         }
     155              : 
     156              :         // clear layout for note-based fingerings
     157        59130 :         for (EngravingItem* e : note->el()) {
     158          432 :             if (e->isFingering()) {
     159          396 :                 Fingering* f = toFingering(e);
     160          396 :                 if (f->layoutType() == ElementType::NOTE) {
     161            8 :                     f->setPos(PointF());
     162            8 :                     f->setbbox(RectF());
     163              :                 }
     164              :             }
     165              :         }
     166              :     }
     167              : 
     168              :     // A chord can have its own arpeggio and also be part of another arpeggio's span.  We need to lay out both of these arpeggios properly
     169        49847 :     Arpeggio* oldSpanArp = item->spanArpeggio();
     170        49847 :     Arpeggio* newSpanArp = nullptr;
     171              : 
     172              :     // If item has an arpeggio: mark chords which are part of the arpeggio
     173        49847 :     if (item->arpeggio()) {
     174          212 :         item->arpeggio()->findAndAttachToChords();
     175          212 :         item->arpeggio()->mutldata()->maxChordPad = 0.0;
     176          212 :         item->arpeggio()->mutldata()->minChordX = DBL_MAX;
     177          212 :         TLayout::layoutItem(item->arpeggio(), ctx);
     178              :     }
     179              : 
     180        49847 :     if (item->spanArpeggio() != oldSpanArp) {
     181           13 :         newSpanArp = item->spanArpeggio();
     182              :     }
     183              :     // If item is within arpeggio span, keep track of largest space needed between glissando and chord across staves
     184        49847 :     double lllMax = lll;
     185       149541 :     for (Arpeggio* spanArp : { oldSpanArp, newSpanArp }) {
     186        99694 :         if (!spanArp || !spanArp->chord()) {
     187        99420 :             continue;
     188              :         }
     189              : 
     190          274 :         Arpeggio::LayoutData* arpldata = spanArp->mutldata();
     191              : 
     192          274 :         const Segment* seg = spanArp->chord()->segment();
     193          274 :         const EngravingItem* endItem = seg->element(spanArp->endTrack());
     194          274 :         const Chord* endChord = item;
     195          274 :         if (endItem && endItem->isChord()) {
     196          274 :             endChord = toChord(endItem);
     197              :         }
     198              : 
     199              :         // If a note is covered in the voice span but located outside the visual span of the arpeggio calculate accidental offset later
     200              :         bool aboveStart
     201          274 :             = std::make_pair(item->vStaffIdx(), item->downLine()) < std::make_pair(spanArp->vStaffIdx(), spanArp->chord()->upLine());
     202          274 :         bool belowEnd = std::make_pair(item->vStaffIdx(), item->upLine()) > std::make_pair(endChord->vStaffIdx(), endChord->downLine());
     203              : 
     204          274 :         if (!(aboveStart || belowEnd)) {
     205          274 :             ElementType elType = spanArp->type();
     206          274 :             const PaddingTable& paddingTable = item->score()->paddingTables().horizontalPaddingTable();
     207          274 :             double arpeggioNoteDistance = paddingTable.at(elType).at(ElementType::NOTE) * mag_;
     208          274 :             double arpeggioLedgerDistance = paddingTable.at(elType).at(ElementType::LEDGER_LINE) * mag_;
     209          274 :             int firstLedgerBelow = item->staff()->lines(item->downNote()->tick()) * 2 - 1;
     210          274 :             int firstLedgerAbove = -1;
     211              : 
     212          274 :             double gapSize = arpeggioNoteDistance;
     213              : 
     214          274 :             if (leftNote && muse::RealIsNull(leftNote->x())) {
     215          245 :                 if (downnote->line() > firstLedgerBelow || upnote->line() < firstLedgerAbove) {
     216           94 :                     gapSize = arpeggioLedgerDistance + ctx.conf().styleS(Sid::ledgerLineLength).val() * item->spatium();
     217              :                 }
     218           29 :             } else if (leftNote && (leftNote->line() > firstLedgerBelow || leftNote->line() < firstLedgerAbove)) {
     219           11 :                 gapSize = arpeggioLedgerDistance + ctx.conf().styleS(Sid::ledgerLineLength).val() * item->spatium();
     220              :             }
     221              : 
     222          274 :             double arpChordX = std::min(chordX, 0.0);
     223              : 
     224          274 :             if (!chordAccidentals.empty()) {
     225           18 :                 double arpeggioAccidentalDistance = paddingTable.at(elType).at(ElementType::ACCIDENTAL) * mag_;
     226           18 :                 double accidentalDistance = ctx.conf().styleAbsolute(Sid::accidentalDistance) * mag_;
     227           18 :                 gapSize = arpeggioAccidentalDistance - accidentalDistance;
     228           18 :                 gapSize -= ArpeggioLayout::insetDistance(spanArp, ctx, mag_, item, chordAccidentals);
     229              :             }
     230              : 
     231          274 :             double extraX = spanArp->width() + gapSize;
     232              : 
     233              :             // Track leftmost chord position, as we we always want the arpeggio to be to the left of this
     234          274 :             arpldata->minChordX = std::min(arpldata->minChordX, arpChordX);
     235              : 
     236              :             // Save this to arpeggio if largest
     237          274 :             arpldata->maxChordPad = std::max(arpldata->maxChordPad, lll + extraX);
     238              : 
     239              :             // If first chord in arpeggio set y
     240          274 :             if (item->arpeggio() && item->arpeggio() == spanArp) {
     241          212 :                 double y1 = upnote->pos().y() - upnote->headHeight() * .5;
     242          212 :                 item->arpeggio()->mutldata()->setPosY(y1);
     243              :             }
     244              : 
     245          274 :             Note* endDownNote = endChord->downNote();
     246              : 
     247              :             // If last chord in arpeggio, set x
     248          274 :             if (endDownNote->track() == item->track()) {
     249              :                 // Amount to move arpeggio from it's parent chord to factor in chords further to the left
     250          213 :                 double firstChordX = spanArp->chord()->ldata()->pos().x();
     251          213 :                 double xDiff = firstChordX - arpldata->minChordX;
     252              : 
     253          213 :                 double offset = -(xDiff + arpldata->maxChordPad);
     254          213 :                 spanArp->mutldata()->setPosX(offset);
     255          213 :                 if (spanArp->visible()) {
     256          211 :                     lllMax = std::max(lllMax, offset);
     257              :                 }
     258              :             }
     259              :         }
     260              :     }
     261              : 
     262        49847 :     lll = lllMax;
     263              : 
     264        49847 :     if (item->dots()) {
     265          891 :         double x = item->dotPosX() + dotNoteDistance
     266          891 :                    + double(item->dots() - 1) * ctx.conf().styleAbsolute(Sid::dotDotDistance) * mag_;
     267          891 :         x += item->symWidth(SymId::augmentationDot);
     268          891 :         rrr = std::max(rrr, x);
     269              :     }
     270              : 
     271        49847 :     if (item->hook()) {
     272         2305 :         if (item->beam()) {
     273            9 :             ctx.mutDom().undoRemoveElement(item->hook());
     274              :         } else {
     275         2296 :             TLayout::layoutHook(item->hook(), item->hook()->mutldata());
     276         2296 :             if (item->up() && item->stem()) {
     277              :                 // hook position is not set yet
     278         1070 :                 double x = item->hook()->ldata()->bbox().right() + item->stem()->flagPosition().x() + chordX;
     279         1070 :                 rrr = std::max(rrr, x);
     280              :             }
     281              :         }
     282              :     }
     283              : 
     284        49847 :     item->setSpaceLw(lll);
     285        49847 :     item->setSpaceRw(rrr);
     286              : 
     287       108545 :     for (Note* note : item->notes()) {
     288        58698 :         layoutNote2(note, ctx);
     289              :     }
     290              : 
     291        49999 :     for (EngravingItem* e : item->el()) {
     292              :         // Cannot layout slurs as chord positions are not fixed
     293              :         // Chord brackets should be the outermost element
     294          152 :         if (e->isSlur() || e->isChordBracket()) {
     295            0 :             continue;
     296              :         }
     297          152 :         TLayout::layoutItem(e, ctx);
     298          152 :         if (e->isChordLine()) {
     299          152 :             RectF tbbox = e->ldata()->bbox().translated(e->pos());
     300          152 :             double lx = tbbox.left() + chordX;
     301          152 :             double rx = tbbox.right() + chordX;
     302          152 :             if (-lx > item->spaceLw()) {
     303           65 :                 item->setSpaceLw(item->spaceLw() - lx);
     304              :             }
     305          152 :             if (rx > item->spaceRw()) {
     306           87 :                 item->setSpaceRw(rx);
     307              :             }
     308              :         }
     309              :     }
     310              : 
     311              :     // align note-based fingerings
     312        49847 :     std::vector<Fingering*> alignNote;
     313        49847 :     double xNote = DBL_MAX;
     314       108545 :     for (Note* note : item->notes()) {
     315        58698 :         bool leftFound = false;
     316        59130 :         for (EngravingItem* e : note->el()) {
     317          432 :             if (e->isFingering() && e->autoplace()) {
     318          396 :                 Fingering* f = toFingering(e);
     319          396 :                 if (f->layoutType() == ElementType::NOTE && f->textStyleType() == TextStyleType::LH_GUITAR_FINGERING) {
     320            8 :                     alignNote.push_back(f);
     321            8 :                     if (!leftFound) {
     322            8 :                         leftFound = true;
     323            8 :                         double xf = f->ldata()->pos().x();
     324            8 :                         xNote = std::min(xNote, xf);
     325              :                     }
     326              :                 }
     327              :             }
     328              :         }
     329              :     }
     330        49855 :     for (Fingering* f : alignNote) {
     331            8 :         f->mutldata()->setPosX(xNote);
     332              :     }
     333              : 
     334        49847 :     layoutLvArticulation(item, ctx);
     335              : 
     336        49847 :     createParenGroups(item);
     337        49847 :     ParenthesisLayout::layoutChordParentheses(item, ctx);
     338              : 
     339        49847 :     fillShape(item, item->mutldata(), ctx.conf());
     340        49847 : }
     341              : 
     342          830 : void ChordLayout::layoutTablature(Chord* item, LayoutContext& ctx)
     343              : {
     344          830 :     double _spatium          = item->spatium();
     345          830 :     double mag_ = item->staff() ? item->staff()->staffMag(item) : 1.0;    // palette elements do not have a staff
     346          830 :     double dotNoteDistance = ctx.conf().styleAbsolute(Sid::dotNoteDistance) * mag_;
     347          830 :     double minNoteDistance = ctx.conf().styleAbsolute(Sid::minNoteDistance) * mag_;
     348          830 :     double minTieLength = ctx.conf().styleAbsolute(Sid::minTieLength) * mag_;
     349              : 
     350          862 :     for (Chord* c : item->graceNotes()) {
     351           32 :         layoutTablature(c, ctx);
     352              :     }
     353              : 
     354          830 :     double lll         = 0.0;                    // space to leave at left of chord
     355          830 :     double rrr         = 0.0;                    // space to leave at right of chord
     356          830 :     Note* upnote       = item->upNote();
     357          830 :     double headWidth   = item->symWidth(SymId::noteheadBlack);
     358          830 :     const Staff* st    = item->staff();
     359          830 :     const StaffType* tab = st->staffTypeForElement(item);
     360          830 :     double lineDist    = tab->lineDistance().val() * _spatium;
     361          830 :     double stemX       = 0.5 * headWidth;
     362          830 :     int ledgerLines = 0;
     363          830 :     double llY         = 0.0;
     364              : 
     365          830 :     size_t numOfNotes  = item->notes().size();
     366          830 :     double minY        = 1000.0;                 // just a very large value
     367         1858 :     for (size_t i = 0; i < numOfNotes; ++i) {
     368         1028 :         Note* note = item->notes().at(i);
     369         1028 :         note->updateFrettingForTiesAndBends();
     370         1028 :         ChordLayout::setDotRelativeLine(note, 0, ctx);
     371         1028 :         TLayout::layoutNote(note, note->mutldata());
     372              :         // set headWidth to max fret text width
     373         1028 :         double fretWidth = note->ldata()->bbox().width();
     374         1028 :         if (headWidth < fretWidth) {
     375          117 :             headWidth = fretWidth;
     376              :         }
     377              :         // centre fret string on stem
     378         1028 :         double x = stemX - fretWidth * 0.5;
     379         1028 :         double y = note->fixed() ? note->line() * lineDist / 2 : tab->physStringToYOffset(note->string()).toAbsolute(_spatium);
     380         1028 :         note->setPos(x, y);
     381         1028 :         if (y < minY) {
     382         1009 :             minY  = y;
     383              :         }
     384         1028 :         int currLedgerLines   = tab->numOfTabLedgerLines(note->string());
     385         1028 :         if (currLedgerLines > ledgerLines) {
     386            0 :             ledgerLines = currLedgerLines;
     387            0 :             llY         = y;
     388              :         }
     389              : 
     390              :         // allow extra space for shortened ties; this code must be kept synchronized
     391              :         // with the tie positioning code in Tie::slurPos()
     392              :         // but the allocation of space needs to be performed here
     393              :         Tie* tie;
     394         1028 :         tie = note->tieBack();
     395         1028 :         if (tie && tie->addToSkyline()) {
     396           37 :             SlurTieLayout::calculateDirection(tie);
     397           37 :             double overlap = 0.0;                // how much tie can overlap start and end notes
     398           37 :             bool shortStart = false;            // whether tie should clear start note or not
     399           37 :             Note* startNote = tie->startNote();
     400           37 :             Chord* startChord = startNote ? startNote->chord() : nullptr;
     401           37 :             if (startChord && startChord->measure() == item->measure() && startChord == Navigation::prevChordRest(item)) {
     402           11 :                 double startNoteWidth = startNote->width();
     403              :                 // overlap into start chord?
     404              :                 // if in start chord, there are several notes or stem and tie in same direction
     405           11 :                 if (startChord->notes().size() > 1 || (startChord->stem() && startChord->up() == tie->up())) {
     406              :                     // clear start note (1/8 of fret mark width)
     407            2 :                     shortStart = true;
     408            2 :                     overlap -= startNoteWidth * 0.125;
     409              :                 } else {            // overlap start note (by ca. 1/3 of fret mark width)
     410            9 :                     overlap += startNoteWidth * 0.35;
     411              :                 }
     412              :                 // overlap into end chord (this)?
     413              :                 // if several notes or neither stem or tie are up
     414           11 :                 if (item->notes().size() > 1 || (item->stem() && !item->up() && !tie->up())) {
     415              :                     // for positive offset:
     416              :                     //    use available space
     417              :                     // for negative x offset:
     418              :                     //    space is allocated elsewhere, so don't re-allocate here
     419            2 :                     if (!RealIsNull(note->ldata()->pos().x())) {                      // this probably does not work for TAB, as
     420            2 :                         overlap += std::abs(note->ldata()->pos().x());              // _pos is used to centre the fret on the stem
     421              :                     } else {
     422            0 :                         overlap -= fretWidth * 0.125;
     423              :                     }
     424              :                 } else {
     425            9 :                     if (shortStart) {
     426            0 :                         overlap += fretWidth * 0.15;
     427              :                     } else {
     428            9 :                         overlap += fretWidth * 0.35;
     429              :                     }
     430              :                 }
     431           11 :                 double d = std::max(minTieLength - overlap, 0.0);
     432           11 :                 lll = std::max(lll, d);
     433              :             }
     434              :         }
     435              :     }
     436              : 
     437              :     // create ledger lines, if required (in some historic styles)
     438          830 :     if (ledgerLines > 0) {
     439            0 :         item->resizeLedgerLinesTo(ledgerLines);
     440            0 :         double extraLen    = 0;
     441            0 :         double llX         = stemX - (headWidth + extraLen) * 0.5;
     442            0 :         for (int i = 0; i < ledgerLines; i++) {
     443            0 :             LedgerLine* ldgLin = item->ledgerLines()[i];
     444            0 :             ldgLin->setTrack(item->track());
     445            0 :             ldgLin->setVisible(item->visible());
     446            0 :             ldgLin->setLen(headWidth + extraLen);
     447            0 :             ldgLin->setPos(llX, llY);
     448            0 :             TLayout::layoutLedgerLine(ldgLin, ctx);
     449            0 :             llY += lineDist / ledgerLines;
     450              :         }
     451            0 :         headWidth += extraLen;            // include ledger lines extra width in chord width
     452              :     }
     453              : 
     454              :     // horiz. spacing: leave half width at each side of the (potential) stem
     455          830 :     double halfHeadWidth = headWidth * 0.5;
     456          830 :     if (lll < stemX - halfHeadWidth) {
     457            0 :         lll = stemX - halfHeadWidth;
     458              :     }
     459          830 :     if (rrr < stemX + halfHeadWidth) {
     460          830 :         rrr = stemX + halfHeadWidth;
     461              :     }
     462              :     // align dots to the widest fret mark (not needed in all TAB styles, but harmless anyway)
     463          830 :     item->setDotPosX(headWidth);
     464              : 
     465          830 :     if (item->shouldHaveStem()) {
     466              :         // if stem is required but missing, add it;
     467              :         // set stem position (stem length is set in Chord:layoutStem() )
     468          115 :         if (!item->stem()) {
     469            0 :             Stem* stem = Factory::createStem(item);
     470            0 :             stem->setGenerated(true);
     471            0 :             ctx.mutDom().addElement(stem);
     472              :         }
     473          115 :         item->stem()->setPos(StemLayout::tabStemPos(item, tab) * _spatium);
     474          115 :         if (item->hook()) {
     475            3 :             if (item->beam()) {
     476            0 :                 ctx.mutDom().undoRemoveElement(item->hook());
     477              :             } else {
     478            3 :                 if (rrr < stemX + item->hook()->width()) {
     479            3 :                     rrr = stemX + item->hook()->width();
     480              :                 }
     481              : 
     482            3 :                 item->hook()->setPos(item->stem()->flagPosition());
     483              :             }
     484              :         }
     485              :     } else {
     486          715 :         if (item->stem()) {
     487            0 :             ctx.mutDom().doUndoRemoveElement(item->stem());
     488            0 :             item->remove(item->stem());
     489              :         }
     490          715 :         if (item->hook()) {
     491            0 :             ctx.mutDom().doUndoRemoveElement(item->hook());
     492            0 :             item->remove(item->hook());
     493              :         }
     494          715 :         if (item->beam()) {
     495            0 :             ctx.mutDom().doUndoRemoveElement(item->beam());
     496            0 :             item->remove(item->beam());
     497              :         }
     498              :     }
     499              : 
     500          830 :     if (!tab->genDurations()                           // if tab is not set for duration symbols
     501           24 :         || track2voice(item->track())                        // or not in first voice
     502          854 :         || (item->isGrace()                                  // no tab duration symbols if grace notes
     503            0 :             && item->beamMode() == BeamMode::AUTO)) {      // and beammode == AUTO
     504              :         //
     505          806 :         delete item->tabDur();       // delete an existing duration symbol
     506          806 :         item->setTabDur(nullptr);
     507              :     } else {
     508              :         //
     509              :         // tab duration symbols
     510              :         //
     511              :         // if no previous CR
     512              :         // OR symbol repeat set to ALWAYS
     513              :         // OR symbol repeat condition is triggered
     514              :         // OR duration type and/or number of dots is different from current CR
     515              :         // OR chord beam mode not AUTO
     516              :         // OR previous CR is a rest
     517              :         // AND no not-stem
     518              :         // set a duration symbol (trying to re-use existing symbols where existing to minimize
     519              :         // symbol creation and deletion)
     520           24 :         bool needTabDur = false;
     521           24 :         bool repeat = false;
     522           24 :         if (!item->noStem()) {
     523              :             // check duration of prev. CR segm
     524           24 :             ChordRest* prevCR = Navigation::prevChordRest(item);
     525           24 :             if (prevCR == 0) {
     526           13 :                 needTabDur = true;
     527           11 :             } else if (item->beamMode() != BeamMode::AUTO
     528            7 :                        || prevCR->durationType().type() != item->durationType().type()
     529            7 :                        || prevCR->dots() != item->dots()
     530            7 :                        || prevCR->tuplet() != item->tuplet()
     531           18 :                        || prevCR->isRest()) {
     532            4 :                 needTabDur = true;
     533            7 :             } else if (tab->symRepeat() == TablatureSymbolRepeat::ALWAYS
     534            7 :                        || ((tab->symRepeat() == TablatureSymbolRepeat::MEASURE
     535            0 :                             || tab->symRepeat() == TablatureSymbolRepeat::SYSTEM)
     536            0 :                            && item->measure() != prevCR->measure())) {
     537            7 :                 needTabDur = true;
     538            7 :                 repeat = true;
     539              :             }
     540              :         }
     541           24 :         if (needTabDur) {
     542              :             // symbol needed; if not exist, create; if exists, update duration
     543           24 :             if (!item->tabDur()) {
     544            5 :                 item->setTabDur(new TabDurationSymbol(item, tab, item->durationType().type(), item->dots()));
     545              :             } else {
     546           19 :                 item->tabDur()->setDuration(item->durationType().type(), item->dots(), tab);
     547              :             }
     548           24 :             item->tabDur()->setRepeat(repeat);
     549              : //                  _tabDur->setMag(mag());           // useless to set grace mag: graces have no dur. symbol
     550           24 :             TLayout::layoutTabDurationSymbol(item->tabDur(), item->tabDur()->mutldata());
     551           24 :             if (minY < 0) {                           // if some fret extends above tab body (like bass strings)
     552            0 :                 TabDurationSymbol::LayoutData* tdladata = item->tabDur()->mutldata();
     553            0 :                 tdladata->moveY(minY);             // raise duration symbol
     554            0 :                 tdladata->setBbox(tdladata->bbox().translated(0, minY));
     555              :             }
     556              :         } else {                                // symbol not needed: if exists, delete
     557            0 :             delete item->tabDur();
     558            0 :             item->setTabDur(nullptr);
     559              :         }
     560              :     }                                     // end of if(duration_symbols)
     561              : 
     562          830 :     if (item->arpeggio()) {
     563           21 :         item->arpeggio()->findAndAttachToChords();
     564           21 :         double y = upnote->pos().y() - upnote->headHeight() * .5;
     565           21 :         TLayout::layoutArpeggio(item->arpeggio(), item->arpeggio()->mutldata(), ctx.conf());
     566           21 :         lll += item->arpeggio()->width() + _spatium * .5;
     567           21 :         item->arpeggio()->setPos(-lll, y);
     568              : 
     569              :         // handle the special case of _arpeggio->span() > 1
     570              :         // in layoutArpeggio2() after page layout has done so we
     571              :         // know the y position of the next staves
     572              :     }
     573              : 
     574              :     // allocate enough room for glissandi
     575          830 :     if (item->endsNoteAnchoredLine()) {
     576           35 :         if (!item->rtick().isZero()) {                          // if not at beginning of measure
     577           28 :             lll += _spatium * 0.5 + minTieLength;
     578              :         }
     579              :         // special case of system-initial glissando final note is handled in Glissando::layout() itself
     580              :     }
     581              : 
     582          830 :     if (item->hook()) {
     583            3 :         if (item->beam()) {
     584            0 :             ctx.mutDom().undoRemoveElement(item->hook());
     585            3 :         } else if (tab == 0) {
     586            0 :             TLayout::layoutHook(item->hook(), item->hook()->mutldata());
     587            0 :             if (item->up()) {
     588              :                 // hook position is not set yet
     589            0 :                 double x = item->hook()->ldata()->bbox().right() + item->stem()->flagPosition().x();
     590            0 :                 rrr = std::max(rrr, x);
     591              :             }
     592              :         }
     593              :     }
     594              : 
     595          830 :     if (item->dots()) {
     596           16 :         double x = 0.0;
     597              :         // if stems are beside staff, dots are placed near to stem
     598           16 :         if (!tab->stemThrough()) {
     599              :             // if there is an unbeamed hook, dots should start after the hook
     600           15 :             if (item->hook() && !item->beam()) {
     601            0 :                 x = item->hook()->width() + dotNoteDistance;
     602              :             }
     603              :             // if not, dots should start at a fixed distance right after the stem
     604              :             else {
     605           15 :                 x = STAFFTYPE_TAB_DEFAULTDOTDIST_X.toAbsolute(_spatium);
     606              :             }
     607           15 :             item->setDotPosX(x);
     608              :         }
     609              :         // if stems are through staff, use dot position computed above on fret mark widths
     610              :         else {
     611            1 :             x = item->dotPosX() + dotNoteDistance
     612            1 :                 + (item->dots() - 1) * ctx.conf().styleS(Sid::dotDotDistance).val() * _spatium;
     613              :         }
     614           16 :         x += item->symWidth(SymId::augmentationDot);
     615           16 :         rrr = std::max(rrr, x);
     616              :     }
     617              : 
     618          830 :     item->setSpaceLw(lll);
     619          830 :     item->setSpaceRw(rrr);
     620              : 
     621          830 :     double graceMag = ctx.conf().styleD(Sid::graceNoteMag);
     622              : 
     623          830 :     std::vector<Chord*> graceNotesBefore = item->Chord::graceNotesBefore();
     624          830 :     size_t nb = graceNotesBefore.size();
     625          830 :     if (nb) {
     626           27 :         double xl = -(item->spaceLw() + minNoteDistance);
     627           56 :         for (int i = static_cast<int>(nb) - 1; i >= 0; --i) {
     628           29 :             Chord* c = graceNotesBefore.at(i);
     629           29 :             xl -= c->spaceRw() /* * 1.2*/;
     630           29 :             c->setPos(xl, 0);
     631           29 :             xl -= c->spaceLw() + minNoteDistance * graceMag;
     632              :         }
     633           27 :         if (-xl > item->spaceLw()) {
     634           27 :             item->setSpaceLw(item->spaceLw() - xl);
     635              :         }
     636              :     }
     637          830 :     std::vector<Chord*> gna = item->graceNotesAfter();
     638          830 :     size_t na = gna.size();
     639          830 :     if (na) {
     640              :         // get factor for start distance after main note. Values found by testing.
     641              :         double fc;
     642            2 :         switch (item->durationType().type()) {
     643            0 :         case DurationType::V_LONG:    fc = 3.8;
     644            0 :             break;
     645            0 :         case DurationType::V_BREVE:   fc = 3.8;
     646            0 :             break;
     647            0 :         case DurationType::V_WHOLE:   fc = 3.8;
     648            0 :             break;
     649            0 :         case DurationType::V_HALF:    fc = 3.6;
     650            0 :             break;
     651            2 :         case DurationType::V_QUARTER: fc = 2.1;
     652            2 :             break;
     653            0 :         case DurationType::V_EIGHTH:  fc = 1.4;
     654            0 :             break;
     655            0 :         case DurationType::V_16TH:    fc = 1.2;
     656            0 :             break;
     657            0 :         default: fc = 1;
     658              :         }
     659            2 :         double xr = fc * (item->spaceRw() + minNoteDistance);
     660            5 :         for (int i = 0; i <= static_cast<int>(na) - 1; i++) {
     661            3 :             Chord* c = gna.at(i);
     662            3 :             xr += c->spaceLw() * (i == 0 ? 1.3 : 1);
     663            3 :             c->setPos(xr, 0);
     664            3 :             xr += c->spaceRw() + minNoteDistance * graceMag;
     665              :         }
     666            2 :         if (xr > item->spaceRw()) {
     667            2 :             item->setSpaceRw(xr);
     668              :         }
     669              :     }
     670          846 :     for (EngravingItem* e : item->el()) {
     671           16 :         TLayout::layoutItem(e, ctx);
     672           16 :         if (e->isChordLine()) {
     673           16 :             RectF tbbox = e->ldata()->bbox().translated(e->pos());
     674           16 :             double lx = tbbox.left();
     675           16 :             double rx = tbbox.right();
     676           16 :             if (-lx > item->spaceLw()) {
     677           12 :                 item->setSpaceLw(item->spaceLw() - lx);
     678              :             }
     679           16 :             if (rx > item->spaceRw()) {
     680            4 :                 item->setSpaceRw(rx);
     681              :             }
     682              :         }
     683              :     }
     684              : 
     685         1858 :     for (size_t i = 0; i < numOfNotes; ++i) {
     686         1028 :         layoutNote2(item->notes().at(i), ctx);
     687              :     }
     688              : 
     689          830 :     if (item->stemSlash()) {
     690            1 :         TLayout::layoutStemSlash(item->stemSlash(), item->stemSlash()->mutldata(), ctx.conf());
     691              :     }
     692              : 
     693          830 :     layoutLvArticulation(item, ctx);
     694              : 
     695          830 :     createParenGroups(item);
     696          830 :     ParenthesisLayout::layoutChordParentheses(item, ctx);
     697              : 
     698          830 :     fillShape(item, item->mutldata(), ctx.conf());
     699          830 : }
     700              : 
     701        50677 : void ChordLayout::layoutLvArticulation(Chord* item, LayoutContext& ctx)
     702              : {
     703              :     // Laissez vib ties must be layed out with the rest of the note, as they need to be taken into account for horizontal spacing
     704        52385 :     for (Articulation* a : item->articulations()) {
     705         1708 :         if (a->isLaissezVib()) {
     706              :             // Must set direction before laying out
     707            0 :             if (item->measure()->hasVoices(a->staffIdx(), item->tick(), item->actualTicks()) && a->anchor() == ArticulationAnchor::AUTO) {
     708            0 :                 a->setUp(item->up());         // if there are voices place articulation at stem
     709            0 :             } else if (a->anchor() == ArticulationAnchor::AUTO) {
     710            0 :                 a->setUp(!item->up());         // place articulation at note head
     711              :             } else {
     712            0 :                 a->setUp(a->anchor() == ArticulationAnchor::TOP);
     713              :             }
     714              : 
     715            0 :             TLayout::layoutItem(a, ctx);
     716              : 
     717            0 :             const Articulation::LayoutData* ldata = a->ldata();
     718            0 :             const double sp = a->spatium();
     719            0 :             const Note* note = ldata->up() ? item->upNote() : item->downNote();
     720            0 :             const int upDir = ldata->up() ? -1 : 1;
     721            0 :             const double noteHeight = note->height();
     722              : 
     723            0 :             PointF result = note->pos() + item->pos();
     724            0 :             double center = SlurTieLayout::noteOpticalCenterForTie(note, ldata->up);
     725            0 :             Spatium visualInsetSp = 0.0_sp;
     726            0 :             if (note->headGroup() == NoteHeadGroup::HEAD_SLASH || note->shouldHideFret()) {
     727            0 :                 visualInsetSp = 0.2_sp;
     728            0 :             } else if (item->up() && ldata->up) {
     729            0 :                 visualInsetSp = 0.7_sp;
     730              :             } else {
     731            0 :                 visualInsetSp = 0.1_sp;
     732              :             }
     733            0 :             double visualInset = visualInsetSp.toAbsolute(sp);
     734              : 
     735            0 :             result.rx() += center + visualInset - ldata->bbox().left();
     736            0 :             result.ry() += (noteHeight / 2 + 0.2 * sp) * upDir;
     737              : 
     738            0 :             a->setPos(result);
     739              :         }
     740              :     }
     741        50677 : }
     742              : 
     743              : //---------------------------------------------------------
     744              : //   layoutNotesSpanners
     745              : //---------------------------------------------------------
     746              : 
     747        49421 : void ChordLayout::layoutSpanners(Chord* item, LayoutContext& ctx)
     748              : {
     749       108058 :     for (const Note* n : item->notes()) {
     750        58637 :         Tie* tie = n->tieFor();
     751        58637 :         if (tie) {
     752         2280 :             TLayout::layoutTie(tie, ctx);
     753              :         }
     754        59283 :         for (Spanner* sp : n->spannerBack()) {
     755          646 :             TLayout::layoutSpanner(sp, ctx);
     756              :         }
     757        59283 :         for (Spanner* sp : n->spannerFor()) {
     758          646 :             if ((sp->isGlissando() || sp->isGuitarBend()) && sp->tick2() >= ctx.state().page()->endTick()) {
     759            0 :                 TLayout::layoutSpanner(sp, ctx);
     760              :             }
     761              :         }
     762              :     }
     763        49421 : }
     764              : 
     765              : //---------------------------------------------------------
     766              : //   layoutArticulations
     767              : //    layout tenuto and staccato
     768              : //    called before layouting slurs
     769              : //---------------------------------------------------------
     770              : 
     771       101931 : void ChordLayout::layoutArticulations(Chord* item, LayoutContext& ctx)
     772              : {
     773       103745 :     for (Chord* gc : item->graceNotes()) {
     774         1814 :         layoutArticulations(gc, ctx);
     775              :     }
     776              : 
     777       101931 :     if (item->articulations().empty()) {
     778        98757 :         return;
     779              :     }
     780         3174 :     const Staff* st = item->staff();
     781         3174 :     const StaffType* staffType = st->staffTypeForElement(item);
     782         3174 :     double mag            = (staffType->isSmall() ? ctx.conf().styleD(Sid::smallStaffMag) : 1.0) * staffType->userMag();
     783         3174 :     double _spatium       = ctx.conf().spatium() * mag;
     784         3174 :     double _lineDist       = _spatium * staffType->lineDistance().val() / 2;
     785         3174 :     const double minDist = ctx.conf().styleAbsolute(Sid::articulationMinDistance) * mag;
     786         3174 :     const ArticulationStemSideAlign articulationHAlign = ctx.conf().styleV(Sid::articulationStemHAlign).value<ArticulationStemSideAlign>();
     787         3174 :     const bool keepArticsTogether = ctx.conf().styleB(Sid::articulationKeepTogether);
     788         3174 :     const double stemSideDistance = ctx.conf().styleAbsolute(Sid::propertyDistanceStem) * mag;
     789         3174 :     const double headSideDistance = ctx.conf().styleAbsolute(Sid::propertyDistanceHead) * mag;
     790         3174 :     const double tenutoAdditionalTieDistance = 0.6 * _spatium;
     791         3174 :     const double staccatoAdditionalTieDistance = 0.4 * _spatium;
     792              : 
     793         3174 :     int numCloseArtics = 0;
     794         3174 :     bool hasStaffArticsUp = false;
     795         3174 :     bool hasStaffArticsDown = false;
     796         3174 :     if (keepArticsTogether) {
     797              :         // find out how many close-to-note artics there are, and whether there is a staff-anchored artic to align to
     798         6627 :         for (Articulation* a : item->articulations()) {
     799         3454 :             if (!a->visible()) {
     800           50 :                 continue;
     801              :             }
     802         3404 :             if (a->layoutCloseToNote()) {
     803         1327 :                 ++numCloseArtics;
     804              :             } else {
     805         2077 :                 if (a->up()) {
     806         1823 :                     hasStaffArticsUp = true;
     807              :                 } else {
     808          254 :                     hasStaffArticsDown = true;
     809              :                 }
     810         2077 :                 if (hasStaffArticsDown && hasStaffArticsUp) {
     811            1 :                     break;           // nothing more to be learned now
     812              :                 }
     813              :             }
     814              :         }
     815              :     }
     816              : 
     817              :     //
     818              :     //    determine direction
     819              :     //    place tenuto and staccato
     820              :     //
     821              : 
     822         3174 :     Articulation* prevVisibleArticulation = nullptr;
     823         6628 :     for (Articulation* a : item->articulations()) {
     824         3454 :         if (item->measure()->hasVoices(a->staffIdx(), item->tick(), item->actualTicks()) && a->anchor() == ArticulationAnchor::AUTO) {
     825          220 :             a->setUp(item->up());         // if there are voices place articulation at stem
     826         3234 :         } else if (a->anchor() == ArticulationAnchor::AUTO) {
     827         2530 :             if (a->isOrnament()) {
     828          628 :                 bool oddVoice = item->track() % 2;
     829          628 :                 a->setUp(!oddVoice);
     830         1902 :             } else if (a->symId() >= SymId::articMarcatoAbove && a->symId() <= SymId::articMarcatoTenutoBelow) {
     831           93 :                 a->setUp(true);         // Gould, p. 117: strong accents above staff
     832              :             } else {
     833         1809 :                 a->setUp(!item->up());         // place articulation at note head
     834              :             }
     835              :         } else {
     836          704 :             a->setUp(a->anchor() == ArticulationAnchor::TOP);
     837              :         }
     838              : 
     839         3454 :         if (!a->layoutCloseToNote()) {
     840         2115 :             continue;
     841              :         }
     842              : 
     843         1339 :         bool bottom = !a->up();      // true: articulation is below chord;  false: articulation is above chord
     844         1339 :         TLayout::layoutItem(a, ctx);     // must be done after assigning direction, or else symId is not reliable
     845         1339 :         bool leaveTieSpace = leaveSpaceForTie(a);
     846              : 
     847         1339 :         bool headSide = bottom == item->up();
     848         1339 :         double y = 0.0;
     849              :         double x;
     850         1339 :         if ((!headSide || !a->isBasicArticulation()) && item->stem()) {
     851          170 :             switch (articulationHAlign) {
     852            0 :             case ArticulationStemSideAlign::STEM:
     853            0 :                 x = item->stem()->width() * .5;
     854            0 :                 break;
     855            0 :             case ArticulationStemSideAlign::NOTEHEAD:
     856            0 :                 x = item->up() ? item->downNote()->noteheadCenterX() : centerX(item);
     857            0 :                 break;
     858          170 :             case ArticulationStemSideAlign::AVERAGE:
     859              :             default:
     860          170 :                 x = item->up() ? (item->stem()->width() * .5 + item->downNote()->noteheadCenterX()) * .5
     861          105 :                     : (item->stem()->width() * .5 + centerX(item)) * .5;
     862          170 :                 break;
     863              :             }
     864          170 :             if (item->up()) {
     865           65 :                 x = item->downNote()->pos().x() + item->downNote()->bboxRightPos() - x;
     866              :             }
     867              :         } else {
     868         1169 :             x = centerX(item);
     869              :         }
     870              : 
     871         1339 :         if (bottom) {
     872          532 :             if (!headSide && item->stem()) {
     873          105 :                 double stemBottom = item->stem()->ldata()->bbox().translated(item->stem()->pos()).bottom();
     874              :                 // Check if there's a hook, because the tip of the hook always extends slightly past the end of the stem
     875          105 :                 if (item->hook()) {
     876           16 :                     y = item->hook()->ldata()->bbox().translated(item->hook()->pos()).bottom();
     877              :                 } else {
     878           89 :                     y = item->stem()->ldata()->bbox().translated(item->stem()->pos()).bottom();
     879              :                 }
     880          105 :                 int line   = round((y + _lineDist) / _lineDist);
     881          105 :                 int lines = 2 * (staffType->lines() - 1);
     882          105 :                 if (line < lines && !(line % 2)) {
     883            0 :                     line += 1;
     884              :                 }
     885          105 :                 double dist = (line * _lineDist) - stemBottom;
     886          105 :                 bool hasBeam = item->beam() || (item->tremoloTwoChord());
     887          105 :                 if (line < lines && hasBeam && dist < stemSideDistance) {
     888              :                     // beams can give stems weird unpredictable lengths, so we should enforce min
     889              :                     // distance even inside the staff
     890              : 
     891              :                     // adjust downwards a space but stop at the bottom of the staff
     892            0 :                     line = std::min(line + 2, lines);
     893              :                 }
     894          105 :                 if (line < lines) {        // align between staff lines
     895            0 :                     y = line * _lineDist;
     896            0 :                     y -= a->height() * .5;
     897          105 :                 } else if (line == lines) {
     898            0 :                     y = stemSideDistance + (lines * _lineDist);
     899              :                 } else {
     900          105 :                     y += stemSideDistance;
     901              :                 }
     902              :             } else {
     903          427 :                 x = centerX(item);
     904          427 :                 int lines = (staffType->lines() - 1) * 2;
     905          427 :                 int line = std::max(item->downLine(), -1);
     906          427 :                 bool adjustArtic = (a->up() && hasStaffArticsUp) || (!a->up() && hasStaffArticsDown);
     907          427 :                 if (keepArticsTogether && adjustArtic && numCloseArtics > 0) {
     908           17 :                     line = std::max(line, lines - (1 + (numCloseArtics * 2)));
     909              :                 }
     910          427 :                 if (line < lines - 1) {
     911          104 :                     if (leaveTieSpace && line % 2) {
     912            0 :                         line += 1;
     913              :                     }
     914          104 :                     y = ((line & ~1) + 3) * _lineDist;
     915          104 :                     y -= a->height() * .5;
     916              :                 } else {
     917          323 :                     y = item->downPos() + 0.5 * item->downNote()->headHeight() + headSideDistance;
     918          323 :                     if (leaveTieSpace) {
     919            0 :                         y += a->isStaccato() ? staccatoAdditionalTieDistance : tenutoAdditionalTieDistance;
     920              :                     }
     921              :                 }
     922              :             }
     923          532 :             if (prevVisibleArticulation && (prevVisibleArticulation->up() == a->up())) {
     924           74 :                 int staffBottom = (staffType->lines() - 2) * 2;
     925           66 :                 if ((headSide && item->downLine() < staffBottom)
     926          140 :                     || (!headSide && !muse::RealIsEqualOrMore(y, (staffBottom + 1) * _lineDist))) {
     927           12 :                     y += _spatium;
     928              :                 } else {
     929           62 :                     y += prevVisibleArticulation->height() + minDist;
     930              :                 }
     931              :             }
     932              :             // center symbol
     933              :         } else { // topside
     934          807 :             if (!headSide && item->stem()) {
     935           65 :                 double stemTop = item->stem()->ldata()->bbox().translated(item->stem()->pos()).top();
     936              :                 // Check if there's a hook, because the tip of the hook always extends slightly past the end of the stem
     937           65 :                 if (item->hook()) {
     938           12 :                     y = item->hook()->ldata()->bbox().translated(item->hook()->pos()).top();
     939              :                 } else {
     940           53 :                     y = item->stem()->ldata()->bbox().translated(item->stem()->pos()).top();
     941              :                 }
     942           65 :                 int line = round((y - _lineDist) / _lineDist);
     943           65 :                 if (line > 0 && !(line % 2)) {
     944            4 :                     line -= 1;
     945              :                 }
     946           65 :                 double dist = stemTop - (line * _lineDist);
     947           65 :                 bool hasBeam = item->beam() || item->tremoloTwoChord();
     948           65 :                 if (line > 0 && hasBeam && dist < stemSideDistance) {
     949              :                     // beams can give stems weird unpredictable lengths, so we should enforce min
     950              :                     // distance even inside the staff
     951              : 
     952              :                     // adjust upwards a space but stop at the top of the staff
     953            0 :                     line = std::max(line - 2, 0);
     954              :                 }
     955              : 
     956           65 :                 if (line > 0) {        // align between staff lines
     957            6 :                     y = line * _lineDist;
     958            6 :                     y += a->height() * .5;
     959           59 :                 } else if (line == 0) {
     960            0 :                     y = -stemSideDistance;
     961              :                 } else {
     962           59 :                     y -= stemSideDistance;
     963              :                 }
     964              :             } else {
     965          742 :                 x = centerX(item);
     966          742 :                 int lines = (staffType->lines() - 1) * 2;
     967          742 :                 int line = std::min(item->upLine(), lines + 1);
     968          742 :                 bool adjustArtic = (a->up() && hasStaffArticsUp) || (!a->up() && hasStaffArticsDown);
     969          742 :                 if (keepArticsTogether && adjustArtic && numCloseArtics > 0) {
     970           75 :                     line = std::min(line, 1 + (numCloseArtics * 2));
     971              :                 }
     972          742 :                 if (line > 1) {
     973          408 :                     if (leaveTieSpace && line % 2) {
     974            0 :                         line -= 1;
     975              :                     }
     976          408 :                     y = (((line + 1) & ~1) - 3) * _lineDist;
     977          408 :                     y += a->height() * .5;
     978              :                 } else {
     979          334 :                     y = item->upPos() - 0.5 * item->downNote()->headHeight() - headSideDistance;
     980          334 :                     if (leaveTieSpace) {
     981            4 :                         y -= a->isStaccato() ? staccatoAdditionalTieDistance : tenutoAdditionalTieDistance;
     982              :                     }
     983              :                 }
     984              :             }
     985          807 :             if (prevVisibleArticulation && (prevVisibleArticulation->up() == a->up())) {
     986           30 :                 if ((headSide && item->upLine() > 2) || (!headSide && !muse::RealIsEqualOrLess(y, 0.0))) {
     987           26 :                     y -= item->spatium();
     988              :                 } else {
     989            4 :                     y -= prevVisibleArticulation->height() + minDist;
     990              :                 }
     991              :             }
     992              :         }
     993         1339 :         x -= a->ldata()->opticalCenter();
     994         1339 :         a->setPos(x, y);
     995         1339 :         if (a->visible()) {
     996         1327 :             prevVisibleArticulation = a;
     997              :         }
     998              : //            measure()->system()->staff(a->staffIdx())->skyline().add(a->shape().translated(a->pos() + segment()->pos() + measure()->pos()));
     999              :     }
    1000              : }
    1001              : 
    1002              : //---------------------------------------------------------
    1003              : //   layoutArticulations2
    1004              : //    Called after layouting systems
    1005              : //    Tentatively layout all articulations
    1006              : //    To be finished after laying out slurs
    1007              : //---------------------------------------------------------
    1008              : 
    1009       101931 : void ChordLayout::layoutArticulations2(Chord* item, LayoutContext& ctx, bool layoutOnCrossBeamSide)
    1010              : {
    1011       101931 :     ArticulationStemSideAlign articulationHAlign = ctx.conf().styleV(Sid::articulationStemHAlign).value<ArticulationStemSideAlign>();
    1012       103745 :     for (Chord* gc : item->graceNotes()) {
    1013         1814 :         layoutArticulations2(gc, ctx);
    1014              :     }
    1015              : 
    1016       101931 :     if (item->articulations().empty()) {
    1017        98757 :         return;
    1018              :     }
    1019         3174 :     double headSideX = centerX(item);
    1020         3174 :     double stemSideX = headSideX;
    1021         3174 :     if (item->stem()) {
    1022         3016 :         switch (articulationHAlign) {
    1023            0 :         case ArticulationStemSideAlign::STEM:
    1024            0 :             stemSideX = item->stem()->width() * .5;
    1025            0 :             break;
    1026            0 :         case ArticulationStemSideAlign::NOTEHEAD:
    1027            0 :             stemSideX = item->up() ? item->downNote()->noteheadCenterX() : centerX(item);
    1028            0 :             break;
    1029         3016 :         case ArticulationStemSideAlign::AVERAGE:
    1030              :         default:
    1031         3016 :             stemSideX = item->up() ? (item->stem()->width() * .5 + item->downNote()->noteheadCenterX()) * .5
    1032         1798 :                         : (item->stem()->width() * .5 + centerX(item)) * .5;
    1033         3016 :             break;
    1034              :         }
    1035         3016 :         if (item->up()) {
    1036         1218 :             stemSideX = item->downNote()->pos().x() + item->downNote()->bboxRightPos() - stemSideX;
    1037              :         }
    1038              :     }
    1039              : 
    1040         3174 :     double stacAccentKern = 0.2 * item->spatium();
    1041         3174 :     double mag = item->mag();
    1042         3174 :     double minDist = ctx.conf().styleAbsolute(Sid::articulationMinDistance) * mag;
    1043         3174 :     double staffDist = ctx.conf().styleAbsolute(Sid::propertyDistance) * mag;
    1044         3174 :     double stemDist = ctx.conf().styleAbsolute(Sid::propertyDistanceStem) * mag;
    1045         3174 :     double noteDist = ctx.conf().styleAbsolute(Sid::propertyDistanceHead) * mag;
    1046         3174 :     double yOffset = item->staffOffsetY();
    1047              : 
    1048         3174 :     double chordTopY = item->upPos() - 0.5 * item->upNote()->headHeight() + yOffset;       // note position of highest note
    1049         3174 :     double chordBotY = item->downPos() + 0.5 * item->upNote()->headHeight() + yOffset;     // note position of lowest note
    1050              : 
    1051         3174 :     double staffTopY = -staffDist + yOffset;
    1052         3174 :     double staffBotY = item->staff()->staffHeight(item->tick()) + staffDist + yOffset;
    1053              : 
    1054              :     // avoid collisions of staff articulations with chord notes:
    1055              :     // gap between note and staff articulation is distance0 + 0.5 spatium
    1056              : 
    1057         3174 :     if (item->stem()) {
    1058              :         // Check if there's a hook, because the tip of the hook always extends slightly past the end of the stem
    1059         3016 :         if (item->up()) {
    1060         1218 :             double tip = item->hook()
    1061         1218 :                          ? item->hook()->ldata()->bbox().translated(item->hook()->pos()).top()
    1062         1133 :                          : item->stem()->ldata()->bbox().translated(item->stem()->pos()).top();
    1063              : 
    1064         1218 :             chordTopY = tip + yOffset;
    1065              :         } else {
    1066         1798 :             double tip = item->hook()
    1067         1798 :                          ? item->hook()->ldata()->bbox().translated(item->hook()->pos()).bottom()
    1068         1734 :                          : item->stem()->ldata()->bbox().translated(item->stem()->pos()).bottom();
    1069              : 
    1070         1798 :             chordBotY = tip + yOffset;
    1071              :         }
    1072              :     }
    1073              : 
    1074              :     //
    1075              :     //    place all articulations with anchor at chord/rest
    1076              :     //
    1077         3174 :     chordTopY -= item->up() ? stemDist : noteDist;
    1078         3174 :     chordBotY += item->up() ? noteDist : stemDist;
    1079              :     // add space for staccato and tenuto marks which may have been previously laid out
    1080         6628 :     for (Articulation* a : item->articulations()) {
    1081         3454 :         ArticulationAnchor aa = a->anchor();
    1082         3454 :         if (a->layoutCloseToNote() && a->visible() && aa == ArticulationAnchor::AUTO) {
    1083         1241 :             if (a->up()) {
    1084          747 :                 chordTopY = a->y() - a->height() - minDist + yOffset;
    1085              :             } else {
    1086          494 :                 chordBotY = a->y() + a->height() + minDist + yOffset;
    1087              :             }
    1088              :         }
    1089              :     }
    1090              : 
    1091         6628 :     for (Articulation* a : item->articulations()) {
    1092         3454 :         if (layoutOnCrossBeamSide && !a->isOnCrossBeamSide()) {
    1093            0 :             continue;
    1094              :         }
    1095         3454 :         if (!a->layoutCloseToNote()) {
    1096         2115 :             continue;
    1097              :         }
    1098         1339 :         if (a->up()) {
    1099          807 :             if (a->visible()) {
    1100          803 :                 chordTopY = a->y() - a->height() - minDist + yOffset;
    1101              :             }
    1102              :         } else {
    1103          532 :             if (a->visible()) {
    1104          524 :                 chordBotY = a->y() + a->height() + minDist + yOffset;
    1105              :             }
    1106              :         }
    1107              :     }
    1108              :     //
    1109              :     //    now place all articulations with staff top or bottom anchor, or chord anchor for artics that don't layout close to note
    1110              :     //
    1111         3174 :     staffTopY = std::min(staffTopY, chordTopY);
    1112         3174 :     staffBotY = std::max(staffBotY, chordBotY);
    1113         3174 :     Articulation* stacc = nullptr;
    1114         6628 :     for (Articulation* a : item->articulations()) {
    1115         3454 :         double kearnHeight = 0.0;
    1116         3454 :         if ((layoutOnCrossBeamSide && !a->isOnCrossBeamSide()) || a->isLaissezVib()) {
    1117            0 :             continue;
    1118              :         }
    1119              : 
    1120         3454 :         if (a->isStaccato()) {
    1121          991 :             stacc = a;
    1122          208 :         } else if (stacc && a->isAccent() && stacc->up() == a->up()
    1123         2701 :                    && (muse::RealIsEqualOrLess(stacc->ldata()->pos().y(), 0.0)
    1124         2493 :                        || muse::RealIsEqualOrMore(stacc->ldata()->pos().y(), item->staff()->staffHeight(item->tick())))) {
    1125              :             // obviously, the accent doesn't have a cutout, so this value just artificially moves the stacc
    1126              :             // and accent closer to each other to simulate some kind of kerning. Looks great using all musescore fonts,
    1127              :             // though there is a possibility that a different font which has vertically-asymmetrical accents
    1128              :             // could make this look bad.
    1129           12 :             kearnHeight = stacAccentKern;
    1130           12 :             stacc = nullptr;
    1131              :         } else {
    1132         2451 :             stacc = nullptr;
    1133              :         }
    1134              : 
    1135         3454 :         if (!a->layoutCloseToNote()) {
    1136         2115 :             TLayout::layoutItem(a, ctx);
    1137         2115 :             if (a->up()) {
    1138         1621 :                 a->setPos(!item->up() || !a->isBasicArticulation() ? headSideX : stemSideX, staffTopY + kearnHeight - yOffset);
    1139         1621 :                 if (a->visible()) {
    1140         1589 :                     staffTopY = a->y() - a->height() - minDist + yOffset;
    1141              :                 }
    1142              :             } else {
    1143          494 :                 a->setPos(item->up() || !a->isBasicArticulation() ? headSideX : stemSideX, staffBotY - kearnHeight - yOffset);
    1144          494 :                 if (a->visible()) {
    1145          488 :                     staffBotY = a->y() + a->height() + minDist + yOffset;
    1146              :                 }
    1147              :             }
    1148         2115 :             a->mutldata()->moveX(-a->ldata()->opticalCenter());
    1149              :         }
    1150              : 
    1151         3454 :         if (!a->isOnCrossBeamSide()) {
    1152         3454 :             if (a->layoutCloseToNote()) {
    1153         1339 :                 Measure* meas = a->measure();
    1154         1339 :                 System* sys = meas->system();
    1155         1339 :                 if (sys) {
    1156         1339 :                     sys->staff(a->vStaffIdx())->skyline().add(a->shape().translate(a->systemPos() + item->staffOffset()));
    1157              :                 }
    1158              :             } else {
    1159         2115 :                 Autoplace::autoplaceSegmentElement(a, a->mutldata(), a->up(), true);
    1160              :             }
    1161         3454 :             if (a->addToSkyline()) {
    1162         3404 :                 a->segment()->staffShape(a->vStaffIdx()).add(a->shape().translated(a->pos() + item->pos() + item->staffOffset()));
    1163              :             }
    1164              :         }
    1165              :     }
    1166              : }
    1167              : 
    1168              : //---------------------------------------------------------
    1169              : //   layoutArticulations3
    1170              : //    Called after layouting slurs
    1171              : //    Fix up articulations that need to go outside the slur
    1172              : //---------------------------------------------------------
    1173              : 
    1174         1781 : void ChordLayout::layoutArticulations3(Chord* item, Slur* slur, LayoutContext& ctx)
    1175              : {
    1176              :     SlurSegment* ss;
    1177         1781 :     if (item == slur->startCR()) {
    1178          918 :         ss = slur->frontSegment();
    1179          863 :     } else if (item == slur->endCR()) {
    1180          863 :         ss = slur->backSegment();
    1181              :     } else {
    1182            0 :         return;
    1183              :     }
    1184         1781 :     Segment* s = item->segment();
    1185         1781 :     Measure* m = item->measure();
    1186         1781 :     SysStaff* itemStaff = m->system() ? m->system()->staff(item->vStaffIdx()) : nullptr;
    1187         1781 :     SysStaff* slurStaff = m->system() ? m->system()->staff(ss->vStaffIdx()) : nullptr;
    1188         1781 :     PointF itemStaffPos = itemStaff ? PointF(0.0, itemStaff->y()) : PointF();
    1189         1781 :     PointF slurStaffPos = itemStaff ? PointF(0.0, slurStaff->y()) : PointF();
    1190         1817 :     for (auto iter = item->articulations().begin(); iter != item->articulations().end(); ++iter) {
    1191           36 :         Articulation* a = *iter;
    1192           36 :         if (a->layoutCloseToNote() || !a->autoplace() || !slur->addToSkyline()) {
    1193           30 :             continue;
    1194              :         }
    1195              :         Shape aShape
    1196            6 :             = a->shape().translate(a->pos() + item->pos() + s->pos() + m->pos() + item->staffOffset() + itemStaffPos);
    1197            6 :         Shape sShape = ss->shape().translate(ss->pos() + slurStaffPos);
    1198           12 :         sShape.removeTypes({ ElementType::HAMMER_ON_PULL_OFF_TEXT });
    1199            6 :         double minDist = ctx.conf().styleAbsolute(Sid::articulationMinDistance);
    1200            6 :         bool slurBelowArticulation = a->up() && !(ss->vStaffIdx() < item->vStaffIdx());
    1201            6 :         double vertClearance = slurBelowArticulation ? aShape.verticalClearance(sShape) : sShape.verticalClearance(aShape);
    1202            6 :         if (vertClearance < minDist) {
    1203            2 :             minDist += slur->up()
    1204            4 :                        ? std::max(aShape.minVerticalDistance(sShape), 0.0)
    1205            2 :                        : std::max(sShape.minVerticalDistance(aShape), 0.0);
    1206            2 :             minDist *= slur->up() ? -1 : 1;
    1207            4 :             for (auto iter2 = iter; iter2 != item->articulations().end(); ++iter2) {
    1208            2 :                 Articulation* aa = *iter2;
    1209            2 :                 aa->mutldata()->moveY(minDist);
    1210            2 :                 if (itemStaff && aa->addToSkyline()) {
    1211            2 :                     SystemLayout::updateSkylineForElement(aa, m->system(), minDist);
    1212           22 :                     for (ShapeElement& sh : s->staffShape(item->vStaffIdx()).elements()) {
    1213           20 :                         if (sh.item() == aa) {
    1214           12 :                             sh.translate(0.0, minDist);
    1215              :                         }
    1216              :                     }
    1217              :                 }
    1218              :             }
    1219              :         }
    1220            6 :     }
    1221              : }
    1222              : 
    1223              : //! May be called again when the chord is added to or removed from a beam.
    1224       144130 : void ChordLayout::layoutStem(Chord* item, const LayoutContext& ctx)
    1225              : {
    1226       144144 :     TRACEFUNC;
    1227              : 
    1228              :     LAYOUT_CALL() << "chord: " << item->eid();
    1229              : 
    1230              :     // Stem needs to know hook's bbox and SMuFL anchors.
    1231              :     // This is done before calcDefaultStemLength because the presence or absence of a hook affects stem length
    1232       144130 :     if (item->hook()) {
    1233         3491 :         item->hook()->setHookType(item->up() ? item->durationType().hooks() : -item->durationType().hooks());
    1234         3491 :         TLayout::layoutHook(item->hook(), item->hook()->mutldata());
    1235              :     }
    1236              : 
    1237              :     // we should calculate default stem length for this chord even if it doesn't have a stem
    1238              :     // because this length is used for tremolos or other things that attach to where the stem WOULD be
    1239       144130 :     item->setDefaultStemLength(StemLayout::calcDefaultStemLength(item, ctx));
    1240              : 
    1241       144130 :     if (!item->stem()) {
    1242        10141 :         return;
    1243              :     }
    1244              : 
    1245       133989 :     item->stem()->mutldata()->setPosX(StemLayout::stemPosX(item));
    1246              : 
    1247       133989 :     item->stem()->setBaseLength(Spatium::fromAbsolute(item->defaultStemLength(), item->spatium()));
    1248       133989 :     TLayout::layoutStem(item->stem(), item->stem()->mutldata(), ctx.conf());
    1249              : 
    1250              :     // And now we need to set the position of the flag.
    1251       133989 :     if (item->hook()) {
    1252         3491 :         item->hook()->setPos(item->stem()->flagPosition());
    1253              :     }
    1254              : 
    1255              :     // Add Stem slash
    1256       133989 :     if ((item->showStemSlash()) && !(item->beam() && item->beam()->elements().front() != item)) {
    1257          554 :         if (!item->stemSlash()) {
    1258          142 :             item->add(Factory::createStemSlash(item));
    1259              :         }
    1260          554 :         TLayout::layoutStemSlash(item->stemSlash(), item->stemSlash()->mutldata(), ctx.conf());
    1261       133435 :     } else if (item->stemSlash()) {
    1262           11 :         item->remove(item->stemSlash());
    1263              :     }
    1264       144130 : }
    1265              : 
    1266        51563 : bool ChordLayout::computeUpBeamCase(const Chord* item, Beam* beam)
    1267              : {
    1268        51563 :     if (beam->cross()) {
    1269          124 :         return item->isBelowCrossBeam(beam);
    1270        51439 :     } else if (beam->userModified()) {
    1271           87 :         return isChordPosBelowBeam(item, beam);
    1272              :     }
    1273              : 
    1274        51352 :     return beam->up();
    1275              : }
    1276              : 
    1277        49780 : void ChordLayout::updateLedgerLines(Chord* item, LayoutContext& ctx)
    1278              : {
    1279              :     // initialize for palette
    1280        49780 :     track_idx_t track = 0;                     // the track lines belong to
    1281              :     // the line pos corresponding to the bottom line of the staff
    1282        49780 :     int lineBelow      = 8;                     // assuming 5-lined "staff"
    1283        49780 :     double lineDistance = 1;
    1284        49780 :     bool staffVisible  = true;
    1285        49780 :     int stepOffset = 0;                         // for staff type changes with a step offset
    1286              : 
    1287        49780 :     Segment* segment = item->segment();
    1288              : 
    1289        49780 :     if (segment) {   //not palette
    1290        49780 :         Fraction tick = segment->tick();
    1291        49780 :         staff_idx_t idx = item->staffIdx() + item->staffMove();
    1292        49780 :         track         = staff2track(idx);
    1293        49780 :         const Staff* st     = ctx.dom().staff(idx);
    1294        49780 :         lineBelow     = (st->lines(tick) - 1) * 2;
    1295        49780 :         lineDistance  = st->lineDistance(tick);
    1296        49780 :         staffVisible  = !st->isLinesInvisible(tick);
    1297        49780 :         stepOffset = st->staffType(tick)->stepOffset();
    1298              :     }
    1299              : 
    1300              :     // need ledger lines?
    1301        49780 :     if (item->downLine() + stepOffset <= lineBelow + 1 && item->upLine() + stepOffset >= -1) {
    1302        41941 :         muse::DeleteAll(item->ledgerLines());
    1303        41941 :         item->ledgerLines().clear();
    1304        41941 :         return;
    1305              :     }
    1306              : 
    1307              :     // the extra length of a ledger line to be added on each side of the notehead
    1308         7839 :     const double extraLen = ctx.conf().style().styleAbsolute(Sid::ledgerLineLength);
    1309              : 
    1310         7839 :     bool visible = false;
    1311              : 
    1312              :     struct LedgerLineData {
    1313              :         int line;
    1314              :         double minX, maxX;
    1315              :         bool visible;
    1316              :         bool accidental;
    1317              :     };
    1318         7839 :     std::vector<LedgerLineData> ledgerLineData;
    1319              : 
    1320              :     // scan chord notes, collecting visibility and x and y extrema
    1321              :     // NOTE: notes are sorted from bottom to top (line no. decreasing)
    1322              :     // notes are scanned twice from outside (bottom or top) toward the staff
    1323              :     // each pass stops at the first note without ledger lines
    1324         7839 :     int n = static_cast<int>(item->notes().size());
    1325              : 
    1326        23517 :     for (const bool topToBottom : { false, true }) {
    1327        15678 :         const int from = topToBottom ? n - 1 : 0;
    1328        15678 :         const int delta = topToBottom ? -1 : 1;
    1329        15678 :         std::vector<LedgerLineData> vecLines;
    1330        15678 :         double hw = 0.0;
    1331        15678 :         double minX = std::numeric_limits<double>::max();
    1332        15678 :         double maxX = std::numeric_limits<double>::min();
    1333        15678 :         int minLine = 0;
    1334        15678 :         int maxLine = lineBelow;
    1335              : 
    1336        25909 :         for (int i = from; i < n && i >= 0; i += delta) {
    1337        19953 :             const Note* note = item->notes().at(i);
    1338        19953 :             int l = note->line() + stepOffset;
    1339              : 
    1340              :             // if 1st pass and note not below staff or 2nd pass and note not above staff
    1341        19953 :             if ((!topToBottom && l <= lineBelow + 1)
    1342        16687 :                 || (topToBottom && l >= -1)) {
    1343              :                 break; // stop this pass
    1344              :             }
    1345              :             // round line number to even number toward 0
    1346        10231 :             if (l < 0) {
    1347         2961 :                 l = (l + 1) & ~1;
    1348              :             } else {
    1349         7270 :                 l = l & ~1;
    1350              :             }
    1351              : 
    1352        10231 :             if (note->visible()) { // if one note is visible,
    1353        10229 :                 visible = true;    // all lines between it and the staff are visible
    1354              :             }
    1355        10231 :             hw = std::max(hw, note->headWidth());
    1356              : 
    1357              :             // Experimental:
    1358              :             // shorten ledger line to avoid collisions with accidentals
    1359              :             // TODO: do something with the following `accid` flag
    1360              :             //
    1361              :             // bool accid = (note->accidental() && note->line() >= (l-1) && note->line() <= (l+1) );
    1362              : 
    1363              :             // ledger lines need the leftmost point of the notehead with a respect of bbox
    1364        10231 :             const double x = note->pos().x() + note->bboxXShift();
    1365        10231 :             if (x - extraLen * note->mag() < minX) {
    1366         7879 :                 minX = x - extraLen * note->mag();
    1367              :                 // increase width of all lines between this one and the staff
    1368         7888 :                 for (auto& d : vecLines) {
    1369            9 :                     if (!d.accidental && ((l < 0 && d.line >= l) || (l > 0 && d.line <= l))) {
    1370            4 :                         d.minX = minX;
    1371              :                     }
    1372              :                 }
    1373              :             }
    1374              :             // same for left side
    1375        10231 :             if (x + hw + extraLen * note->mag() > maxX) {
    1376         7905 :                 maxX = x + hw + extraLen * note->mag();
    1377         8014 :                 for (auto& d : vecLines) {
    1378          109 :                     if ((l < 0 && d.line >= l) || (l > 0 && d.line <= l)) {
    1379           30 :                         d.maxX = maxX;
    1380              :                     }
    1381              :                 }
    1382              :             }
    1383              : 
    1384              :             // check if note vert. pos. is outside current range
    1385              :             // and, if so, add data for new line(s)
    1386        10231 :             if (l < minLine) {
    1387       196403 :                 for (int i1 = l; i1 < minLine; i1 += 2) {
    1388       194380 :                     vecLines.emplace_back(LedgerLineData {
    1389              :                         /*line=*/ i1,
    1390              :                         /*minX=*/ minX,
    1391              :                         /*maxX=*/ maxX,
    1392              :                         /*visible=*/ visible,
    1393              :                         /*accidental=*/ false
    1394              :                     });
    1395              :                 }
    1396         2023 :                 minLine = l;
    1397              :             }
    1398        10231 :             if (l > maxLine) {
    1399        18012 :                 for (int i1 = maxLine + 2; i1 <= l; i1 += 2) {
    1400        12160 :                     vecLines.emplace_back(LedgerLineData {
    1401              :                         /*line=*/ i1,
    1402              :                         /*minX=*/ minX,
    1403              :                         /*maxX=*/ maxX,
    1404              :                         /*visible=*/ visible,
    1405              :                         /*accidental=*/ false
    1406              :                     });
    1407              :                 }
    1408         5852 :                 maxLine = l;
    1409              :             }
    1410              :         }
    1411        15678 :         if (minLine < 0 || maxLine > lineBelow) {
    1412         7875 :             ledgerLineData.insert(ledgerLineData.end(), vecLines.begin(), vecLines.end());
    1413              :         }
    1414        15678 :     }
    1415              : 
    1416         7839 :     double _spatium = item->spatium();
    1417         7839 :     double stepDistance = lineDistance * 0.5;
    1418         7839 :     item->resizeLedgerLinesTo(ledgerLineData.size());
    1419       214379 :     for (size_t i = 0; i < ledgerLineData.size(); ++i) {
    1420       206540 :         LedgerLineData lld = ledgerLineData[i];
    1421       206540 :         LedgerLine* h = item->ledgerLines()[i];
    1422       206540 :         h->setTrack(track);
    1423       206540 :         h->setVisible(lld.visible && staffVisible);
    1424       206540 :         h->setLen(lld.maxX - lld.minX);
    1425       206540 :         h->setPos(lld.minX, lld.line * _spatium * stepDistance);
    1426              :     }
    1427              : 
    1428       214379 :     for (LedgerLine* ll : item->ledgerLines()) {
    1429       206540 :         TLayout::layoutLedgerLine(ll, ctx);
    1430              :     }
    1431         7839 : }
    1432              : 
    1433           87 : bool ChordLayout::isChordPosBelowBeam(const Chord* item, Beam* beam)
    1434              : {
    1435           87 :     assert(!beam->beamFragments().empty());
    1436              : 
    1437           87 :     Note* baseNote = item->up() ? item->downNote() : item->upNote();
    1438           87 :     double noteY = baseNote->pagePos().y();
    1439              : 
    1440           87 :     PointF startAnchor = beam->startAnchor();
    1441           87 :     PointF endAnchor = beam->endAnchor();
    1442              : 
    1443           87 :     if (startAnchor.isNull() || endAnchor.isNull()) {
    1444           18 :         return beam->cross() ? item->isBelowCrossBeam(beam) : beam->up();
    1445              :     }
    1446              : 
    1447           69 :     if (item == beam->elements().front()) {
    1448           23 :         return noteY > startAnchor.y();
    1449              :     }
    1450              : 
    1451           46 :     if (item == beam->elements().back()) {
    1452           23 :         return noteY > endAnchor.y();
    1453              :     }
    1454              : 
    1455           23 :     PointF noteAnchor = BeamLayout::chordBeamAnchor(beam, item, ChordBeamAnchorType::Middle);
    1456           23 :     double noteX = noteAnchor.x();
    1457              : 
    1458              :     double proportionAlongX
    1459           23 :         = muse::RealIsEqual(startAnchor.x(), endAnchor.x()) ? 0.0 : (noteX - startAnchor.x()) / (endAnchor.x() - startAnchor.x());
    1460           23 :     double desiredY = proportionAlongX * (endAnchor.y() - startAnchor.y()) + startAnchor.y();
    1461           23 :     return noteY > desiredY;
    1462              : }
    1463              : 
    1464            0 : bool ChordLayout::isChordPosBelowTrem(const Chord* item, TremoloTwoChord* trem)
    1465              : {
    1466            0 :     if (!item || !trem || !trem->ldata()->isValid()) {
    1467            0 :         return true;
    1468              :     }
    1469            0 :     Note* baseNote = item->up() ? item->downNote() : item->upNote();
    1470            0 :     double noteY = baseNote->pagePos().y();
    1471              : 
    1472            0 :     PointF startAnchor = trem->startAnchor();
    1473            0 :     PointF endAnchor = trem->endAnchor();
    1474              : 
    1475            0 :     if (startAnchor.isNull() || endAnchor.isNull()) {
    1476            0 :         return trem->crossStaffBeamBetween() ? item->isBelowCrossBeam(trem) : trem->up();
    1477              :     }
    1478              : 
    1479            0 :     if (item == trem->chord1()) {
    1480            0 :         return noteY > startAnchor.y();
    1481              :     }
    1482              : 
    1483            0 :     return noteY > endAnchor.y();
    1484              : }
    1485              : 
    1486          190 : bool ChordLayout::computeUpTremoloCase(const Chord* item, TremoloTwoChord* tremolo, const LayoutContext& ctx)
    1487              : {
    1488          190 :     const Chord* c1 = tremolo->chord1();
    1489          190 :     const Chord* c2 = tremolo->chord2();
    1490          190 :     if (!c1 || !c2) {
    1491            0 :         return true;
    1492              :     }
    1493          190 :     bool cross = c1->staffMove() != c2->staffMove();
    1494          190 :     if (item == c1) {
    1495              :         // we have to lay out the tremolo because it hasn't been laid out at all yet, and we need its direction
    1496           95 :         TremoloLayout::layout(tremolo, ctx);
    1497              :     }
    1498              : 
    1499          190 :     const Measure* measure = item->findMeasure();
    1500          190 :     if (!cross && !tremolo->userModified()) {
    1501          190 :         return tremolo->up();
    1502              :     }
    1503              : 
    1504            0 :     if (!measure->system()) {
    1505              :         // this method will be called later (from Measure::layoutCrossStaff) after the
    1506              :         // system is completely laid out.
    1507              :         // this is necessary because otherwise there's no way to deal with cross-staff beams
    1508              :         // because we don't know how far apart the staves actually are
    1509            0 :         return item->ldata()->up;
    1510              :     }
    1511              : 
    1512            0 :     if (tremolo->userModified()) {
    1513            0 :         return ChordLayout::isChordPosBelowTrem(item, tremolo);
    1514              :     }
    1515              : 
    1516              :     // unmodified cross-staff trem, should be one note per staff
    1517            0 :     if (item->staffMove() != 0) {
    1518            0 :         return item->staffMove() > 0;
    1519              :     }
    1520              : 
    1521            0 :     int otherStaffMove = item->staffMove() == c1->staffMove() ? c2->staffMove() : c1->staffMove();
    1522            0 :     return otherStaffMove < 0;
    1523              : }
    1524              : 
    1525        94101 : void ChordLayout::computeUp(const Chord* item, Chord::LayoutData* ldata, const LayoutContext& ctx)
    1526              : {
    1527              :     LAYOUT_CALL() << LAYOUT_ITEM_INFO(item);
    1528              : 
    1529        94101 :     assert(!item->notes().empty());
    1530              : 
    1531              :     // Beams with one chord will be removed later, ignore
    1532        94101 :     const bool hasBeam = item->beam() && item->beam()->elements().size() > 1;
    1533              : 
    1534        94101 :     const StaffType* tab = item->staff() ? item->staff()->staffTypeForElement(item) : 0;
    1535        94101 :     bool isTabStaff = tab && tab->isTabStaff();
    1536        94101 :     if (isTabStaff) {
    1537          836 :         if (tab->stemless()) {
    1538          591 :             ldata->up = false;
    1539        65346 :             return;
    1540              :         }
    1541          245 :         if (!tab->stemThrough()) {
    1542          209 :             bool staffHasMultipleVoices = item->measure()->hasVoices(item->staffIdx(), item->tick(), item->actualTicks());
    1543          209 :             if (staffHasMultipleVoices) {
    1544            8 :                 bool isTrackEven = item->track() % 2 == 0;
    1545            8 :                 ldata->up = isTrackEven;
    1546            8 :                 return;
    1547              :             }
    1548          201 :             ldata->up = !tab->stemsDown();
    1549          201 :             return;
    1550              :         }
    1551              :     }
    1552              : 
    1553        93301 :     if (item->stemDirection() != DirectionV::AUTO
    1554         7884 :         && !hasBeam
    1555       101185 :         && !item->tremoloTwoChord()) {
    1556         6734 :         ldata->up = item->stemDirection() == DirectionV::UP;
    1557         6734 :         return;
    1558              :     }
    1559              : 
    1560        86567 :     if (item->isUiItem()) {
    1561            0 :         ldata->up = true;
    1562            0 :         return;
    1563              :     }
    1564              : 
    1565        86567 :     if (hasBeam) {
    1566        51563 :         ldata->up = computeUpBeamCase(item, item->beam());
    1567        51563 :         return;
    1568        35004 :     } else if (item->tremoloTwoChord()) {
    1569          190 :         ldata->up = computeUpTremoloCase(item, item->tremoloTwoChord(), ctx);
    1570          190 :         return;
    1571              :     }
    1572              : 
    1573        34814 :     bool staffHasMultipleVoices = item->measure()->hasVoices(item->staffIdx(), item->tick(), item->actualTicks());
    1574        34814 :     if (staffHasMultipleVoices) {
    1575         5244 :         bool isTrackEven = item->track() % 2 == 0;
    1576         5244 :         ldata->up = isTrackEven;
    1577         5244 :         return;
    1578              :     }
    1579              : 
    1580        29570 :     bool isGraceNote = item->noteType() != NoteType::NORMAL;
    1581        29570 :     if (isGraceNote) {
    1582          701 :         ldata->up = true;
    1583          701 :         return;
    1584              :     }
    1585              : 
    1586        28869 :     bool chordIsCrossStaff = item->staffMove() != 0;
    1587        28869 :     if (chordIsCrossStaff) {
    1588          114 :         ldata->up = item->staffMove() > 0;
    1589          114 :         return;
    1590              :     }
    1591              : 
    1592        28755 :     std::vector<int> distances = item->noteDistances();
    1593        28755 :     int direction = ChordLayout::computeAutoStemDirection(distances);
    1594        28755 :     ldata->up = direction > 0;
    1595        28755 : }
    1596              : 
    1597        94213 : void ChordLayout::computeUp(ChordRest* item, const LayoutContext& ctx)
    1598              : {
    1599        94213 :     if (item->isChord()) {
    1600        94101 :         Chord* ch = item_cast<Chord*>(item);
    1601        94101 :         computeUp(ch, ch->mutldata(), ctx);
    1602              :     } else {
    1603              :         // base ChordRest
    1604              :         // Beams with one chord will be removed later, ignore
    1605          112 :         const bool hasBeam = item->beam() && item->beam()->elements().size() > 1;
    1606          112 :         if (hasBeam) {
    1607          112 :             item->mutldata()->up = item->beam()->up();
    1608              :         } else {
    1609            0 :             item->mutldata()->up = true;
    1610              :         }
    1611              :     }
    1612        94213 : }
    1613              : 
    1614              : // return 1 means up, 0 means in the middle, -1 means down
    1615        31612 : int ChordLayout::computeAutoStemDirection(const std::vector<int>& noteDistances)
    1616              : {
    1617        31612 :     int left = 0;
    1618        31612 :     int right = static_cast<int>(noteDistances.size()) - 1;
    1619              : 
    1620        36074 :     while (left <= right) {
    1621        31703 :         int leftNote = noteDistances.at(left);
    1622        31703 :         int rightNote = noteDistances.at(right);
    1623        31703 :         int netDirecting = leftNote + rightNote;
    1624        31703 :         if (netDirecting == 0) {
    1625         4462 :             left++;
    1626         4462 :             right--;
    1627         4462 :             continue;
    1628              :         }
    1629        27241 :         return netDirecting > 0 ? 1 : -1;
    1630              :     }
    1631         4371 :     return 0;
    1632              : }
    1633              : 
    1634              : //---------------------------------------------------------
    1635              : //   layoutSegmentElements
    1636              : //---------------------------------------------------------
    1637              : 
    1638        89296 : static void layoutSegmentElements(Segment* segment, track_idx_t startTrack, track_idx_t endTrack, staff_idx_t staffIdx, LayoutContext& ctx)
    1639              : {
    1640       538868 :     for (track_idx_t track = startTrack; track < endTrack; ++track) {
    1641       449572 :         if (EngravingItem* e = segment->element(track)) {
    1642       108699 :             if (e->vStaffIdx() == staffIdx) {
    1643        88664 :                 TLayout::layoutItem(e, ctx);
    1644              :             }
    1645              :         }
    1646              :     }
    1647        89296 : }
    1648              : 
    1649         1613 : void ChordLayout::skipAccidentals(Segment* segment, track_idx_t startTrack, track_idx_t endTrack)
    1650              : {
    1651         8065 :     for (track_idx_t track = startTrack; track < endTrack; ++track) {
    1652         6452 :         EngravingItem* item = segment->element(track);
    1653         6452 :         if (item && item->isChord()) {
    1654         1768 :             for (Note* note : toChord(item)->notes()) {
    1655          980 :                 Accidental* acc = note->accidental();
    1656          980 :                 if (acc) {
    1657            0 :                     acc->mutldata()->setIsSkipDraw(true);
    1658              :                 }
    1659              :             }
    1660              :         }
    1661              :     }
    1662         1613 : }
    1663              : 
    1664        88074 : ChordPosInfo ChordLayout::calculateChordPosInfo(Segment* segment, staff_idx_t staffIdx, track_idx_t partStartTrack,
    1665              :                                                 track_idx_t partEndTrack, LayoutContext& ctx)
    1666              : {
    1667        88074 :     const Staff* staff = ctx.dom().staff(staffIdx);
    1668              : 
    1669        88074 :     ChordPosInfo posInfo = ChordPosInfo();
    1670              : 
    1671       532758 :     for (track_idx_t track = partStartTrack; track < partEndTrack; ++track) {
    1672       444684 :         EngravingItem* e = segment->element(track);
    1673       444684 :         const bool calcChordPos = e && e->isChord() && toChord(e)->vStaffIdx() == staffIdx;
    1674       444684 :         if (!calcChordPos) {
    1675       395584 :             continue;
    1676              :         }
    1677              : 
    1678        49100 :         Chord* chord = toChord(e);
    1679        49100 :         posInfo.chords.push_back(chord);
    1680        49100 :         bool hasGraceBefore = false;
    1681        49991 :         for (Chord* c : chord->graceNotes()) {
    1682          891 :             if (c->isGraceBefore()) {
    1683          656 :                 hasGraceBefore = true;
    1684              :             }
    1685          891 :             layoutChords2(c->notes(), c->up(), ctx);     // layout grace note noteheads
    1686         2673 :             layoutChords3({ c }, c->notes(), staff, ctx);     // layout grace note chords
    1687              :         }
    1688        49100 :         if (chord->up()) {
    1689        25196 :             ++posInfo.upVoices;
    1690        25196 :             posInfo.upStemNotes.insert(posInfo.upStemNotes.end(), chord->notes().begin(), chord->notes().end());
    1691        25196 :             posInfo.upDots   = std::max(posInfo.upDots, chord->dots());
    1692        25196 :             posInfo.maxUpMag = std::max(posInfo.maxUpMag, chord->mag());
    1693        25196 :             if (!posInfo.upHooks && !chord->beam()) {
    1694        17470 :                 posInfo.upHooks = chord->hook();
    1695              :             }
    1696        25196 :             if (hasGraceBefore) {
    1697          300 :                 posInfo.upGrace = true;
    1698              :             }
    1699              :         } else {
    1700        23904 :             ++posInfo.downVoices;
    1701        23904 :             posInfo.downStemNotes.insert(posInfo.downStemNotes.end(), chord->notes().begin(), chord->notes().end());
    1702        23904 :             posInfo.downDots = std::max(posInfo.downDots, chord->dots());
    1703        23904 :             posInfo.maxDownMag = std::max(posInfo.maxDownMag, chord->mag());
    1704        23904 :             if (!posInfo.downHooks && !chord->beam()) {
    1705        15835 :                 posInfo.downHooks = chord->hook();
    1706              :             }
    1707        23904 :             if (hasGraceBefore) {
    1708          193 :                 posInfo.downGrace = true;
    1709              :             }
    1710              :         }
    1711              :     }
    1712              : 
    1713        88074 :     return posInfo;
    1714            0 : }
    1715              : 
    1716        46028 : void ChordLayout::calculateMaxNoteWidths(ChordPosInfo& posInfo, const Fraction& tick, const Staff* staff, LayoutContext& ctx)
    1717              : {
    1718        46028 :     double nominalWidth  = ctx.conf().noteHeadWidth() * staff->staffMag(tick);
    1719        46028 :     posInfo.maxUpWidth   = nominalWidth * posInfo.maxUpMag;
    1720        46028 :     posInfo.maxDownWidth = nominalWidth * posInfo.maxDownMag;
    1721              : 
    1722              :     // layout upstem noteheads
    1723        46028 :     if (posInfo.upVoices > 1) {
    1724          152 :         std::sort(posInfo.upStemNotes.begin(), posInfo.upStemNotes.end(),
    1725          232 :                   [](Note* n1, const Note* n2) ->bool { return n1->stringOrLine() > n2->stringOrLine(); });
    1726              :     }
    1727        46028 :     if (posInfo.upVoices) {
    1728        25037 :         double hw = layoutChords2(posInfo.upStemNotes, true, ctx);
    1729        25037 :         posInfo.maxUpWidth = std::max(posInfo.maxUpWidth, hw);
    1730              :     }
    1731              : 
    1732              :     // layout downstem noteheads
    1733        46028 :     if (posInfo.downVoices > 1) {
    1734           72 :         std::sort(posInfo.downStemNotes.begin(), posInfo.downStemNotes.end(),
    1735           99 :                   [](Note* n1, const Note* n2) ->bool { return n1->stringOrLine() > n2->stringOrLine(); });
    1736              :     }
    1737        46028 :     if (posInfo.downVoices) {
    1738        23832 :         double hw = layoutChords2(posInfo.downStemNotes, false, ctx);
    1739        23832 :         posInfo.maxDownWidth = std::max(posInfo.maxDownWidth, hw);
    1740              :     }
    1741        46028 : }
    1742              : 
    1743        46028 : void ChordLayout::applyChordOffsets(Segment* segment, staff_idx_t staffIdx, track_idx_t partStartTrack, track_idx_t partEndTrack,
    1744              :                                     OffsetInfo& offsetInfo, const ChordPosInfo& posInfo, LayoutContext& ctx)
    1745              : {
    1746        46028 :     const Staff* staff = ctx.dom().staff(staffIdx);
    1747        46028 :     const bool isTab = staff->isTabStaff(segment->tick());
    1748              : 
    1749              :     // apply chord offsets
    1750       275276 :     for (track_idx_t track = partStartTrack; track < partEndTrack; ++track) {
    1751       229248 :         EngravingItem* e = segment->element(track);
    1752       229248 :         const bool layoutChord = e && e->isChord() && toChord(e)->vStaffIdx() == staffIdx;
    1753       229248 :         if (!layoutChord) {
    1754       180148 :             continue;
    1755              :         }
    1756        49100 :         Chord* chord = toChord(e);
    1757        49100 :         Chord::LayoutData* chordLdata = chord->mutldata();
    1758              :         // only centre chords if we are separating voices and this voice has no collision
    1759        49100 :         const bool combineVoices = chord->shouldCombineVoice();
    1760        49100 :         if (!combineVoices && !muse::contains(offsetInfo.tracksToAdjust, track)) {
    1761            0 :             if (chord->up()) {
    1762            0 :                 chordLdata->moveX(offsetInfo.centerUp);
    1763              :             } else {
    1764            0 :                 chordLdata->moveX(offsetInfo.centerDown);
    1765              :             }
    1766            0 :             continue;
    1767              :         }
    1768              : 
    1769        49100 :         if (chord->up()) {
    1770        25196 :             if (!muse::RealIsNull(offsetInfo.upOffset)) {
    1771          376 :                 offsetInfo.oversizeUp = isTab ? offsetInfo.oversizeUp / 2 : offsetInfo.oversizeUp;
    1772          376 :                 chordLdata->moveX(offsetInfo.upOffset + offsetInfo.centerAdjustUp + offsetInfo.oversizeUp);
    1773          376 :                 if (posInfo.downDots && !posInfo.upDots) {
    1774            2 :                     chordLdata->moveX(offsetInfo.dotAdjust);
    1775              :                 }
    1776              :             } else {
    1777        24820 :                 chordLdata->moveX(offsetInfo.centerUp);
    1778              :             }
    1779              :         } else {
    1780        23904 :             if (!muse::RealIsNull(offsetInfo.downOffset)) {
    1781           55 :                 chordLdata->moveX(offsetInfo.downOffset + offsetInfo.centerAdjustDown);
    1782           55 :                 if (posInfo.upDots && !posInfo.downDots) {
    1783            0 :                     chordLdata->moveX(offsetInfo.dotAdjust);
    1784              :                 }
    1785              :             } else {
    1786        23849 :                 chordLdata->moveX(offsetInfo.centerDown);
    1787              :             }
    1788              :         }
    1789              :     }
    1790              : 
    1791              :     // layout chords
    1792        46028 :     std::vector<Note*> notes;
    1793        46028 :     if (posInfo.upVoices) {
    1794        25037 :         notes.insert(notes.end(), posInfo.upStemNotes.begin(), posInfo.upStemNotes.end());
    1795              :     }
    1796        46028 :     if (posInfo.downVoices) {
    1797        23832 :         notes.insert(notes.end(), posInfo.downStemNotes.begin(), posInfo.downStemNotes.end());
    1798              :     }
    1799        46028 :     if (posInfo.upVoices + posInfo.downVoices > 1) {
    1800         2923 :         std::sort(notes.begin(), notes.end(),
    1801        18597 :                   [](Note* n1, const Note* n2) ->bool { return n1->stringOrLine() > n2->stringOrLine(); });
    1802              :     }
    1803        46028 :     layoutChords3(posInfo.chords, notes, staff, ctx);
    1804        46028 : }
    1805              : 
    1806        46028 : OffsetInfo ChordLayout::centreChords(const Segment* segment, ChordPosInfo& posInfo, staff_idx_t staffIdx,
    1807              :                                      const Fraction& tick, LayoutContext& ctx)
    1808              : {
    1809        46028 :     const Staff* staff = ctx.dom().staff(staffIdx);
    1810        46028 :     const bool isTab = staff->isTabStaff(segment->tick());
    1811              : 
    1812        46028 :     OffsetInfo offsetInfo = OffsetInfo();
    1813              :     // only center chords on standard staves.  This is a simpler process for TAB and done elsewhere
    1814        46028 :     if (isTab) {
    1815          221 :         return offsetInfo;
    1816              :     }
    1817              : 
    1818        45807 :     double sp = staff->spatium(tick);
    1819        45807 :     double nominalWidth = ctx.conf().noteHeadWidth() * staff->staffMag(tick);
    1820              : 
    1821              :     // only center chords if they differ from nominal by at least this amount
    1822              :     // this avoids unnecessary centering on differences due only to floating point roundoff
    1823              :     // it also allows for the possibility of disabling centering
    1824              :     // for notes only "slightly" larger than nominal, like half notes
    1825              :     // but this will result in them not being aligned with each other between voices
    1826              :     // unless you change to left alignment as described in the comments below
    1827        45807 :     double centerThreshold = 0.01 * sp;
    1828              : 
    1829              :     // amount by which actual width exceeds nominal, adjusted for staff mag() only
    1830        45807 :     double headDiff = posInfo.maxUpWidth - nominalWidth;
    1831              :     // amount by which actual width exceeds nominal, adjusted for staff & chord/note mag()
    1832        45807 :     double headDiff2 = posInfo.maxUpWidth - nominalWidth * (posInfo.maxUpMag / staff->staffMag(tick));
    1833        45807 :     if (headDiff > centerThreshold && !isTab) {
    1834              :         // larger than nominal
    1835         3189 :         offsetInfo.centerUp = headDiff * -0.5;
    1836              :         // maxUpWidth is true width, but we no longer will care about that
    1837              :         // instead, we care only about portion to right of origin
    1838         3189 :         posInfo.maxUpWidth += offsetInfo.centerUp;
    1839              :         // to left align rather than center, delete both of the above
    1840         3189 :         if (headDiff2 > centerThreshold) {
    1841              :             // if max notehead is wider than nominal with chord/note mag() applied
    1842              :             // then noteheads extend to left of origin
    1843              :             // because stemPosX() is based on nominal width
    1844              :             // so we need to correct for that too
    1845         3189 :             offsetInfo.centerUp += headDiff2;
    1846         3189 :             offsetInfo.oversizeUp = headDiff2;
    1847              :         }
    1848        42618 :     } else if (-headDiff > centerThreshold) {
    1849              :         // smaller than nominal
    1850        20919 :         offsetInfo.centerUp = -headDiff * 0.5;
    1851        20919 :         if (headDiff2 > centerThreshold) {
    1852              :             // max notehead is wider than nominal with chord/note mag() applied
    1853              :             // perform same adjustment as above
    1854            7 :             offsetInfo.centerUp += headDiff2;
    1855            7 :             offsetInfo.oversizeUp = headDiff2;
    1856              :         }
    1857        20919 :         offsetInfo.centerAdjustDown = offsetInfo.centerUp;
    1858              :     }
    1859              : 
    1860        45807 :     headDiff = posInfo.maxDownWidth - nominalWidth;
    1861        45807 :     if (headDiff > centerThreshold) {
    1862              :         // larger than nominal
    1863         2696 :         offsetInfo.centerDown = headDiff * -0.5;
    1864              :         // to left align rather than center, change the above to
    1865              :         //centerAdjustUp = headDiff;
    1866         2696 :         posInfo.maxDownWidth = nominalWidth - offsetInfo.centerDown;
    1867        43111 :     } else if (-headDiff > centerThreshold && !isTab) {
    1868              :         // smaller than nominal
    1869        22336 :         offsetInfo.centerDown = -headDiff * 0.5;
    1870        22336 :         offsetInfo.centerAdjustUp = offsetInfo.centerDown;
    1871              :     }
    1872              : 
    1873        45807 :     return offsetInfo;
    1874            0 : }
    1875              : 
    1876        46028 : void ChordLayout::calculateChordOffsets(Segment* segment, staff_idx_t staffIdx, const Fraction& tick,
    1877              :                                         OffsetInfo& offsetInfo, ChordPosInfo& posInfo, LayoutContext& ctx)
    1878              : {
    1879              :     // handle conflict between upstem and downstem chords
    1880        46028 :     if (!posInfo.upVoices || !posInfo.downVoices) {
    1881        43187 :         return;
    1882              :     }
    1883         2841 :     const Staff* staff = ctx.dom().staff(staffIdx);
    1884         2841 :     double sp = staff->spatium(tick);
    1885         2841 :     const bool isTab = staff->isTabStaff(segment->tick());
    1886              : 
    1887         2841 :     Note* bottomUpNote = posInfo.upStemNotes.front();
    1888         2841 :     Note* topDownNote  = posInfo.downStemNotes.back();
    1889         2841 :     int separation = topDownNote->stringOrLine() - bottomUpNote->stringOrLine();
    1890              : 
    1891         2841 :     std::vector<Note*> overlapNotes;
    1892         2841 :     overlapNotes.reserve(8);
    1893              : 
    1894         2841 :     if (separation == 1) {
    1895              :         // second
    1896           34 :         if (posInfo.upDots && !posInfo.downDots) {
    1897            0 :             offsetInfo.upOffset = posInfo.maxDownWidth + 0.1 * sp;
    1898            0 :             offsetInfo.tracksToAdjust.insert(bottomUpNote->track());
    1899              :         } else {
    1900           34 :             offsetInfo.downOffset = posInfo.maxUpWidth;
    1901              :             // align stems if present
    1902           34 :             if (topDownNote->chord()->stem() && bottomUpNote->chord()->stem()) {
    1903           32 :                 const Stem* topDownStem = topDownNote->chord()->stem();
    1904           32 :                 offsetInfo.downOffset -= topDownStem->lineWidthMag();
    1905            2 :             } else if (topDownNote->chord()->durationType().headType() != NoteHeadType::HEAD_BREVIS
    1906            2 :                        && bottomUpNote->chord()->durationType().headType() != NoteHeadType::HEAD_BREVIS) {
    1907              :                 // stemless notes should be aligned as is they were stemmed
    1908              :                 // (except in case of brevis, cause the notehead has the side bars)
    1909            2 :                 offsetInfo.downOffset -= ctx.conf().styleAbsolute(Sid::stemWidth) * topDownNote->chord()->mag();
    1910              :             }
    1911           34 :             offsetInfo.tracksToAdjust.insert(topDownNote->track());
    1912              :         }
    1913         2807 :     } else if (separation < 1) {
    1914              :         // overlap (possibly unison)
    1915              : 
    1916              :         // build list of overlapping notes
    1917          771 :         for (size_t i = 0, n = posInfo.upStemNotes.size(); i < n; ++i) {
    1918          465 :             if (posInfo.upStemNotes[i]->stringOrLine() >= topDownNote->stringOrLine() - 1) {
    1919          396 :                 overlapNotes.push_back(posInfo.upStemNotes[i]);
    1920              :             } else {
    1921           69 :                 break;
    1922              :             }
    1923              :         }
    1924          759 :         for (size_t i = posInfo.downStemNotes.size(); i > 0; --i) {             // loop most probably needs to be in this reverse order
    1925          431 :             if (posInfo.downStemNotes[i - 1]->stringOrLine() <= bottomUpNote->stringOrLine() + 1) {
    1926          384 :                 overlapNotes.push_back(posInfo.downStemNotes[i - 1]);
    1927              :             } else {
    1928           47 :                 break;
    1929              :             }
    1930              :         }
    1931          375 :         std::sort(overlapNotes.begin(), overlapNotes.end(),
    1932          835 :                   [](Note* n1, const Note* n2) ->bool { return n1->stringOrLine() > n2->stringOrLine(); });
    1933              : 
    1934              :         // determine nature of overlap
    1935          375 :         bool shareHeads = true;                   // can all overlapping notes share heads?
    1936          375 :         bool matchPending = false;                // looking for a unison match
    1937          375 :         bool conflictUnison = false;              // unison found
    1938          375 :         bool conflictSecondUpHigher = false;                  // second found
    1939          375 :         bool conflictSecondDownHigher = false;                // second found
    1940          375 :         int lastLine = 1000;
    1941          375 :         track_idx_t lastTrack = muse::nidx;
    1942          375 :         Note* p = overlapNotes[0];
    1943         1155 :         for (size_t i = 0, count = overlapNotes.size(); i < count; ++i) {
    1944          780 :             Note* n = overlapNotes[i];
    1945              :             NoteHeadType nHeadType;
    1946              :             NoteHeadType pHeadType;
    1947          780 :             Chord* nchord = n->chord();
    1948          780 :             Chord* pchord = p->chord();
    1949          780 :             if (n->ldata()->mirror()) {
    1950            0 :                 if (separation < 0) {
    1951              :                     // don't try to share heads if there is any mirroring
    1952            0 :                     shareHeads = false;
    1953              :                     // don't worry about conflicts involving mirrored notes
    1954            0 :                     continue;
    1955              :                 }
    1956              :             }
    1957          780 :             track_idx_t track = n->track();
    1958          780 :             int line = n->stringOrLine();
    1959          780 :             int d = lastLine - line;
    1960          780 :             switch (d) {
    1961           63 :             case 0:
    1962              :                 // unison
    1963           63 :                 conflictUnison = true;
    1964           63 :                 matchPending = false;
    1965           63 :                 nHeadType = (n->headType() == NoteHeadType::HEAD_AUTO) ? n->chord()->durationType().headType() : n->headType();
    1966           63 :                 pHeadType = (p->headType() == NoteHeadType::HEAD_AUTO) ? p->chord()->durationType().headType() : p->headType();
    1967              :                 // the most important rules for sharing noteheads on unisons between voices are
    1968              :                 // that notes must be one same line with same tpc
    1969              :                 // noteheads must be unmirrored and of same group
    1970              :                 // and chords must be same size if notehead is anything other than HEAD_QUARTER
    1971          120 :                 if (n->headGroup() != p->headGroup() || n->tpc() != p->tpc() || n->ldata()->mirror()
    1972           50 :                     || p->ldata()->mirror()
    1973          120 :                     || (nchord->isSmall() != pchord->isSmall()
    1974            0 :                         && (nHeadType != NoteHeadType::HEAD_QUARTER || pHeadType != NoteHeadType::HEAD_QUARTER))) {
    1975           13 :                     shareHeads = false;
    1976              :                 } else {
    1977              :                     // noteheads are potentially shareable
    1978              :                     // it is more subjective at this point
    1979              :                     // current default is to require *either* of the following:
    1980              :                     //    1) both chords have same number of dots, both have stems, and both noteheads are same type and are full size (automatic match)
    1981              :                     // or 2) one or more of the noteheads is not of type AUTO, but is explicitly set to match the other (user-forced match)
    1982              :                     // or 3) exactly one of the noteheads is invisible (user-forced match)
    1983              :                     // thus user can force notes to be shared despite differing number of dots or either being stemless
    1984              :                     // by setting one of the notehead types to match the other or by making one notehead invisible
    1985              :                     // TODO: consider adding a style option, staff properties, or note property to control sharing
    1986           98 :                     if ((nchord->dots() != pchord->dots() || !nchord->stem() || !pchord->stem() || nHeadType != pHeadType
    1987           42 :                          || n->isSmall() || p->isSmall())
    1988            8 :                         && ((n->headType() == NoteHeadType::HEAD_AUTO && p->headType() == NoteHeadType::HEAD_AUTO)
    1989            0 :                             || nHeadType != pHeadType)
    1990          100 :                         && (n->visible() == p->visible())) {
    1991            8 :                         shareHeads = false;
    1992              :                     }
    1993              :                 }
    1994              : 
    1995           63 :                 break;
    1996           51 :             case 1:
    1997              :                 // second
    1998              :                 // trust that this won't be a problem for single unison
    1999           51 :                 if (separation < 0) {
    2000           51 :                     if (n->chord()->up()) {
    2001            1 :                         conflictSecondUpHigher = true;
    2002              :                     } else {
    2003           50 :                         conflictSecondDownHigher = true;
    2004              :                     }
    2005           51 :                     shareHeads = false;
    2006              :                 }
    2007           51 :                 break;
    2008          666 :             default:
    2009              :                 // no conflict
    2010          666 :                 if (matchPending) {
    2011          291 :                     shareHeads = false;
    2012              :                 }
    2013          666 :                 matchPending = true;
    2014              :             }
    2015          780 :             if (!shareHeads) {
    2016          372 :                 offsetInfo.tracksToAdjust.insert({ track, lastTrack });
    2017              :             }
    2018          780 :             p = n;
    2019          780 :             lastLine = line;
    2020          780 :             lastTrack = track;
    2021              :         }
    2022          375 :         if (matchPending) {
    2023          312 :             shareHeads = false;
    2024              :         }
    2025              : 
    2026          375 :         bool conflict = conflictUnison || conflictSecondDownHigher || conflictSecondUpHigher;
    2027          375 :         bool ledgerOverlapAbove = false;
    2028          375 :         bool ledgerOverlapBelow = false;
    2029              : 
    2030          375 :         double ledgerGap = 0.15 * sp;
    2031          375 :         double ledgerLen = ctx.conf().styleS(Sid::ledgerLineLength).val() * sp;
    2032          375 :         int firstLedgerBelow = staff->lines(bottomUpNote->tick()) * 2;
    2033          375 :         int topDownStemLen = 0;
    2034          375 :         if (!conflictUnison && topDownNote->chord()->stem()) {
    2035          299 :             topDownStemLen = std::round(topDownNote->chord()->stem()->ldata()->bbox().height() / sp * 2);
    2036          318 :             if (bottomUpNote->stringOrLine() > firstLedgerBelow - 1 && topDownNote->stringOrLine() < bottomUpNote->stringOrLine()
    2037          318 :                 && topDownNote->stringOrLine() + topDownStemLen >= firstLedgerBelow) {
    2038            4 :                 ledgerOverlapBelow = true;
    2039              :             }
    2040              :         }
    2041              : 
    2042          375 :         int firstLedgerAbove = -2;
    2043          375 :         int bottomUpStemLen = 0;
    2044          375 :         if (!conflictUnison && bottomUpNote->chord()->stem()) {
    2045          294 :             bottomUpStemLen = std::round(bottomUpNote->chord()->stem()->ldata()->bbox().height() / sp * 2);
    2046          303 :             if (topDownNote->stringOrLine() < -1 && topDownNote->stringOrLine() < bottomUpNote->stringOrLine()
    2047          303 :                 && bottomUpNote->stringOrLine() - bottomUpStemLen <= firstLedgerAbove) {
    2048            9 :                 ledgerOverlapAbove = true;
    2049              :             }
    2050              :         }
    2051              : 
    2052              :         // calculate offsets
    2053          375 :         if (shareHeads) {
    2054           66 :             for (int i = static_cast<int>(overlapNotes.size()) - 1; i >= 1; i -= 2) {
    2055           33 :                 Note* previousNote = overlapNotes[i - 1];
    2056           33 :                 Note* n = overlapNotes[i];
    2057           33 :                 const bool skipNote = previousNote->chord()->isNudged() || n->chord()->isNudged();
    2058           33 :                 if (skipNote) {
    2059            0 :                     continue;
    2060              :                 }
    2061           33 :                 const bool prevChordSmall = previousNote->chord()->isSmall();
    2062           33 :                 const bool nChordSmall = n->chord()->isSmall();
    2063           33 :                 if (previousNote->chord()->dots() == n->chord()->dots()) {
    2064              :                     // hide one set of dots
    2065              :                     // Hide the small augmentation dot if present
    2066           33 :                     bool onLine = !(previousNote->line() & 1);
    2067           33 :                     if (prevChordSmall) {
    2068            0 :                         previousNote->setDotsHidden(true);
    2069           33 :                     } else if (nChordSmall) {
    2070            0 :                         n->setDotsHidden(true);
    2071           33 :                     } else if (onLine) {
    2072              :                         // hide dots for lower voice
    2073           12 :                         if (previousNote->voice() & 1) {
    2074            9 :                             previousNote->setDotsHidden(true);
    2075              :                         } else {
    2076            3 :                             n->setDotsHidden(true);
    2077              :                         }
    2078              :                     } else {
    2079              :                         // hide dots for upper voice
    2080           21 :                         if (!(previousNote->voice() & 1)) {
    2081            7 :                             previousNote->setDotsHidden(true);
    2082              :                         } else {
    2083           14 :                             n->setDotsHidden(true);
    2084              :                         }
    2085              :                     }
    2086              :                 }
    2087              :                 // If either chord is small, adjust offset
    2088           33 :                 Chord* smallChord = prevChordSmall ? previousNote->chord() : nullptr;
    2089           33 :                 smallChord = nChordSmall ? n->chord() : smallChord;
    2090           33 :                 if (smallChord && !(prevChordSmall && nChordSmall)) {
    2091            0 :                     if (smallChord->up()) {
    2092            0 :                         offsetInfo.centerUp *= 2;
    2093              :                     } else {
    2094            0 :                         offsetInfo.centerDown = 0;
    2095              :                     }
    2096              :                 }
    2097              :                 // formerly we hid noteheads in an effort to fix playback
    2098              :                 // but this doesn't work for cases where noteheads cannot be shared
    2099              :                 // so better to solve the problem elsewhere
    2100              :             }
    2101          342 :         } else if (conflict && (posInfo.upDots && !posInfo.downDots)) {
    2102            2 :             offsetInfo.upOffset = posInfo.maxDownWidth + 0.1 * sp;
    2103          340 :         } else if (conflictUnison && separation == 0 && (!posInfo.downGrace || posInfo.upGrace)) {
    2104           19 :             offsetInfo.downOffset = posInfo.maxUpWidth + 0.15 * sp;
    2105          321 :         } else if (conflictUnison) {
    2106            9 :             offsetInfo.upOffset = posInfo.maxDownWidth + 0.15 * sp;
    2107          312 :         } else if (conflictSecondUpHigher) {
    2108            1 :             offsetInfo.upOffset = posInfo.maxDownWidth + 0.15 * sp;
    2109          311 :         } else if ((posInfo.downHooks && !posInfo.upHooks) && !(posInfo.upDots && !posInfo.downDots)) {
    2110              :             // Shift by ledger line length if ledger line conflict or just 0.3sp if no ledger lines
    2111            2 :             double adjSpace = (ledgerOverlapAbove || ledgerOverlapBelow) ? ledgerGap + ledgerLen : 0.3 * sp;
    2112            2 :             offsetInfo.downOffset = posInfo.maxUpWidth + adjSpace;
    2113          311 :         } else if (conflictSecondDownHigher) {
    2114           49 :             if (posInfo.downDots && !posInfo.upDots) {
    2115            0 :                 double adjSpace = (ledgerOverlapAbove || ledgerOverlapBelow) ? ledgerGap + ledgerLen : 0.2 * sp;
    2116            0 :                 offsetInfo.downOffset = posInfo.maxUpWidth + adjSpace;
    2117            0 :             } else {
    2118              :                 // Prevent ledger line & notehead collision
    2119              :                 double adjSpace
    2120           49 :                     = (topDownNote->stringOrLine() <= firstLedgerAbove
    2121           49 :                        || bottomUpNote->stringOrLine() >= firstLedgerBelow) ? ledgerLen - ledgerGap - 0.2 * sp : -0.2 * sp;
    2122           49 :                 offsetInfo.upOffset = posInfo.maxDownWidth + adjSpace;
    2123           49 :                 if (posInfo.downHooks) {
    2124            0 :                     bool needsHookSpace = (ledgerOverlapBelow || ledgerOverlapAbove);
    2125            0 :                     Hook* hook = topDownNote->chord()->hook();
    2126            0 :                     double hookSpace = hook ? hook->width() : 0.0;
    2127            0 :                     offsetInfo.upOffset = needsHookSpace ? hookSpace + ledgerLen + ledgerGap : offsetInfo.upOffset + 0.3 * sp;
    2128              :                 }
    2129              :             }
    2130              :         } else {
    2131              :             // no direct conflict, so parts can overlap (downstem on left)
    2132              :             // just be sure that stems clear opposing noteheads and ledger lines
    2133              :             // Stems are in the middle of fret marks on TAB staves
    2134          260 :             double clearLeft = isTab ? StemLayout::stemPosX(bottomUpNote->chord()) : 0.0;
    2135          260 :             double clearRight = isTab ? StemLayout::stemPosX(bottomUpNote->chord()) : 0.0;
    2136          260 :             double overlapPadding = (isTab ? 0 : 0.3) * sp;
    2137          260 :             if (const Stem* topDownStem = topDownNote->chord()->stem()) {
    2138          247 :                 if (ledgerOverlapBelow) {
    2139              :                     // Create space between stem and ledger line below staff
    2140            2 :                     clearLeft += ledgerLen + ledgerGap + topDownStem->lineWidthMag();
    2141              :                 } else {
    2142          245 :                     clearLeft += topDownStem->lineWidthMag() + overlapPadding;
    2143              :                 }
    2144              :             }
    2145          260 :             if (const Stem* bottomUpStem = bottomUpNote->chord()->stem()) {
    2146          242 :                 if (ledgerOverlapAbove) {
    2147              :                     // Create space between stem and ledger line above staff
    2148           18 :                     clearRight += posInfo.maxDownWidth + ledgerLen + ledgerGap - posInfo.maxUpWidth
    2149            9 :                                   + bottomUpStem->lineWidthMag();
    2150              :                 } else {
    2151          233 :                     clearRight += bottomUpStem->lineWidthMag()
    2152          233 :                                   + std::max(posInfo.maxDownWidth - posInfo.maxUpWidth, 0.0) + overlapPadding;
    2153              :                 }
    2154              :             } else {
    2155           18 :                 posInfo.downDots = 0;                 // no need to adjust for dots in this case
    2156              :             }
    2157          260 :             offsetInfo.upOffset = std::max(clearLeft, clearRight);
    2158              :             // Check if there's enough space to tuck under a flag
    2159          260 :             Note* topUpNote = posInfo.upStemNotes.back();
    2160              :             // Move notes out of the way of straight flags
    2161          260 :             int pad = ctx.conf().styleB(Sid::useStraightNoteFlags) ? 2 : 1;
    2162          260 :             bool overlapsFlag = topDownNote->stringOrLine() + topDownStemLen + pad > topUpNote->stringOrLine();
    2163          260 :             if (posInfo.downHooks && (ledgerOverlapBelow || overlapsFlag)) {
    2164              :                 // we will need more space to avoid collision with hook
    2165              :                 // but we won't need as much dot adjustment
    2166            1 :                 Hook* hook = topDownNote->chord()->hook();
    2167            1 :                 double hookWidth = hook ? hook->width() : 0.0;
    2168            1 :                 if (ledgerOverlapBelow) {
    2169            0 :                     offsetInfo.upOffset = hookWidth + ledgerLen + ledgerGap;
    2170              :                 }
    2171            1 :                 if (isTab) {
    2172            0 :                     offsetInfo.upOffset = hookWidth + posInfo.maxDownWidth;
    2173              :                 }
    2174            1 :                 offsetInfo.upOffset = std::max(offsetInfo.upOffset, posInfo.maxDownWidth + 0.1 * sp);
    2175            1 :                 offsetInfo.dotAdjustThreshold = posInfo.maxUpWidth - 0.3 * sp;
    2176              :             }
    2177              :             // if downstem chord is small, don't center
    2178              :             // and we might not need as much dot adjustment either
    2179          260 :             if (offsetInfo.centerDown > 0.0) {
    2180            0 :                 offsetInfo.centerDown = 0.0;
    2181            0 :                 offsetInfo.centerAdjustUp = 0.0;
    2182            0 :                 offsetInfo.dotAdjustThreshold = (offsetInfo.upOffset - posInfo.maxDownWidth) + posInfo.maxUpWidth - 0.3 * sp;
    2183              :             }
    2184              :         }
    2185              :     }
    2186              : 
    2187              :     // adjust for dots
    2188         2841 :     if ((posInfo.upDots && !posInfo.downDots) || (posInfo.downDots && !posInfo.upDots)) {
    2189              :         // only one sets of dots
    2190              :         // place between chords
    2191              :         int dots;
    2192              :         double mag;
    2193           79 :         if (posInfo.upDots) {
    2194           61 :             dots = posInfo.upDots;
    2195           61 :             mag = posInfo.maxUpMag;
    2196              :         } else {
    2197           18 :             dots = posInfo.downDots;
    2198           18 :             mag = posInfo.maxDownMag;
    2199              :         }
    2200           79 :         double dotWidth = segment->symWidth(SymId::augmentationDot);
    2201              :         // first dot
    2202           79 :         offsetInfo.dotAdjust = ctx.conf().styleAbsolute(Sid::dotNoteDistance) + dotWidth;
    2203              :         // additional dots
    2204           79 :         if (dots > 1) {
    2205            0 :             offsetInfo.dotAdjust += ctx.conf().styleAbsolute(Sid::dotDotDistance) * (dots - 1);
    2206              :         }
    2207           79 :         offsetInfo.dotAdjust *= mag;
    2208              :         // only by amount over threshold
    2209           79 :         offsetInfo.dotAdjust = std::max(offsetInfo.dotAdjust - offsetInfo.dotAdjustThreshold, 0.0);
    2210              :     }
    2211         2841 :     if (separation == 1) {
    2212           34 :         offsetInfo.dotAdjust += 0.1 * sp;
    2213              :     }
    2214         2841 : }
    2215              : 
    2216              : //---------------------------------------------------------
    2217              : //   layoutChords1
    2218              : //    - layout upstem and downstem chords
    2219              : //    - offset as necessary to avoid conflict
    2220              : //---------------------------------------------------------
    2221              : 
    2222        89296 : void ChordLayout::layoutChords1(LayoutContext& ctx, Segment* segment, staff_idx_t staffIdx)
    2223              : {
    2224        89311 :     TRACEFUNC;
    2225              :     LAYOUT_CALL() << LAYOUT_ITEM_INFO(segment);
    2226              : 
    2227        89296 :     const Staff* staff = ctx.dom().staff(staffIdx);
    2228        89296 :     const bool isTab = staff->isTabStaff(segment->tick());
    2229        89296 :     const track_idx_t startTrack = staffIdx * VOICES;
    2230        89296 :     const track_idx_t endTrack   = startTrack + VOICES;
    2231        89296 :     const Fraction tick = segment->tick();
    2232        89296 :     const StaffType* staffType = staff->staffType(segment->tick());
    2233              : 
    2234              :     // we need to check all the notes in all the staves of the part so that we don't get weird collisions
    2235              :     // between accidentals etc with moved notes
    2236        89296 :     const Part* part = staff->part();
    2237        89296 :     const TrackRange partTrackRangeOrDefault = part ? part->trackRange() : TrackRange { startTrack, endTrack };
    2238        89296 :     const track_idx_t partStartTrack = partTrackRangeOrDefault.startTrack;
    2239        89296 :     const track_idx_t partEndTrack = partTrackRangeOrDefault.endTrack;
    2240              : 
    2241        89296 :     if (isTab) {
    2242         1613 :         skipAccidentals(segment, startTrack, endTrack);
    2243              :     }
    2244              : 
    2245        89296 :     const bool isSimpleTab = !staff->staffType() || (!staff->staffType()->stemThrough() && !staff->staffType()->isCommonTabStaff());
    2246        89296 :     if (staff && staff->isTabStaff(tick) && isSimpleTab) {
    2247              :         // Positions of notes should be reset in case we are changing from common or full tab with offsets to simple which should have none
    2248        10738 :         for (track_idx_t track = partStartTrack; track < partEndTrack; ++track) {
    2249         9516 :             EngravingItem* e = segment->element(track);
    2250         9516 :             if (e && e->isChord() && toChord(e)->vStaffIdx() == staffIdx) {
    2251          563 :                 Chord* chord = toChord(e);
    2252          563 :                 Chord::LayoutData* chordLdata = chord->mutldata();
    2253          563 :                 chordLdata->setPosX(0.0);
    2254              :             }
    2255              :         }
    2256              : 
    2257         1222 :         layoutSegmentElements(segment, startTrack, endTrack, staffIdx, ctx);
    2258         1222 :         return;
    2259              :     }
    2260              : 
    2261        88074 :     ChordPosInfo posInfo = calculateChordPosInfo(segment, staffIdx, partStartTrack, partEndTrack, ctx);
    2262              : 
    2263        88074 :     if (posInfo.upVoices + posInfo.downVoices && (staffType->stemThrough() || staffType->isCommonTabStaff())) {
    2264              :         // TODO: use track as secondary sort criteria?
    2265              :         // otherwise there might be issues with unisons between voices
    2266              :         // in some corner cases
    2267              : 
    2268        46028 :         calculateMaxNoteWidths(posInfo, tick, staff, ctx);
    2269              : 
    2270        46028 :         OffsetInfo offsetInfo = centreChords(segment, posInfo, staffIdx, tick, ctx);
    2271              : 
    2272        46028 :         calculateChordOffsets(segment, staffIdx, tick, offsetInfo, posInfo, ctx);
    2273              : 
    2274        46028 :         applyChordOffsets(segment, staffIdx, partStartTrack, partEndTrack, offsetInfo, posInfo, ctx);
    2275        46028 :     }
    2276              : 
    2277        88074 :     if (!isTab) {
    2278        87683 :         layoutLedgerLines(posInfo.chords, ctx);
    2279        87683 :         AccidentalsLayout::layoutAccidentals(posInfo.chords, ctx);
    2280       136558 :         for (Chord* chord : posInfo.chords) {
    2281        49764 :             for (Chord* grace : chord->graceNotes()) {
    2282         2667 :                 AccidentalsLayout::layoutAccidentals({ grace }, ctx);
    2283              :             }
    2284              :         }
    2285              :     }
    2286              : 
    2287        88074 :     layoutSegmentElements(segment, partStartTrack, partEndTrack, staffIdx, ctx);
    2288              : 
    2289       137174 :     for (Chord* chord : posInfo.chords) {
    2290        49100 :         Ornament* ornament = chord->findOrnament();
    2291        49100 :         if (ornament && ornament->showCueNote()) {
    2292           16 :             TLayout::layoutOrnamentCueNote(ornament, ctx);
    2293              :         }
    2294              :     }
    2295        89296 : }
    2296              : 
    2297              : //---------------------------------------------------------
    2298              : //   layoutChords2
    2299              : //    - determine which notes need mirroring
    2300              : //    - this is called once for each stem direction
    2301              : //      eg, once for voices 1&3, once for 2&4
    2302              : //      with all notes combined and sorted to resemble one chord
    2303              : //    - return maximum non-mirrored notehead width
    2304              : //---------------------------------------------------------
    2305              : 
    2306        49760 : double ChordLayout::layoutChords2(std::vector<Note*>& notes, bool up, LayoutContext& ctx)
    2307              : {
    2308              :     int startIdx, endIdx, incIdx;
    2309        49760 :     double maxWidth = 0.0;
    2310              : 
    2311              :     // loop in correct direction so that first encountered notehead wins conflict
    2312        49760 :     if (up) {
    2313              :         // loop bottom up
    2314        25924 :         startIdx = 0;
    2315        25924 :         endIdx = int(notes.size());
    2316        25924 :         incIdx = 1;
    2317              :     } else {
    2318              :         // loop top down
    2319        23836 :         startIdx = int(notes.size()) - 1;
    2320        23836 :         endIdx = -1;
    2321        23836 :         incIdx = -1;
    2322              :     }
    2323              : 
    2324        49760 :     int prevLine = 1000;            // line of previous notehead
    2325              :                                     // hack: start high so first note won't show as conflict
    2326        49760 :     bool prevVisible = false;       // was last note visible?
    2327        49760 :     bool mirror = false;            // should current notehead be mirrored?
    2328              :                                     // value is retained and may be used on next iteration
    2329              :                                     // to track mirror status of previous note
    2330        49760 :     bool isLeft   = notes[startIdx]->chord()->up();               // is notehead on left?
    2331        49760 :     Chord* prevChord = notes[startIdx]->chord();
    2332        49760 :     staff_idx_t prevStaffIdx = prevChord->vStaffIdx();        // staff of last note (including staffMove)
    2333        49760 :     track_idx_t prevTrackIdx = prevChord->track();
    2334              : 
    2335       108690 :     for (int idx = startIdx; idx != endIdx; idx += incIdx) {
    2336        58930 :         Note* note    = notes[idx];                         // current note
    2337        58930 :         const int line      = note->stringOrLine();                       // line of current note
    2338        58930 :         Chord* chord  = note->chord();
    2339              : 
    2340        58930 :         const staff_idx_t staffIdx  = chord->vStaffIdx();                 // staff of current note
    2341        58930 :         const track_idx_t trackIdx = chord->track();
    2342        58930 :         const Staff* st = note->staff();
    2343        58930 :         const StaffType* tab = st->staffTypeForElement(note);
    2344        58930 :         const bool isTab = note->staff() && note->staff()->isTabStaff(note->chord()->tick());
    2345              : 
    2346        58930 :         if (isTab) {
    2347              :             // TAB notes need to be laid out to set the fret string so we can calulate their width
    2348              :             // Standard staves can get this information from the notehead symbol, so no need to lay out
    2349          327 :             TLayout::layoutNote(note, note->mutldata());
    2350              :         }
    2351              : 
    2352              :         // there is a conflict
    2353              :         // if this is same or adjacent line as previous note (and chords are on same staff!)
    2354              :         // but no need to do anything about it if either note is invisible
    2355        58930 :         bool sameTrack = trackIdx == prevTrackIdx;
    2356              : 
    2357          660 :         bool conflict = (std::abs(prevLine - line) < 2) && (prevStaffIdx == staffIdx) && note->visible() && prevVisible
    2358        59590 :                         && (sameTrack || Chord::combineVoice(chord, prevChord));
    2359              : 
    2360              :         // this note is on opposite side of stem as previous note
    2361              :         // if there is a conflict
    2362              :         // or if this the first note *after* a conflict
    2363        58930 :         if (conflict || (chord->up() != isLeft)) {
    2364          898 :             isLeft = !isLeft;
    2365              :         }
    2366              : 
    2367              :         // determine if we would need to mirror current note
    2368              :         // to get it to the correct side
    2369              :         // this would be needed to get a note to left or downstem or right of upstem
    2370              :         // whether or not we actually do this is determined later (based on user mirror property)
    2371        58930 :         bool nmirror = (chord->up() != isLeft);
    2372              : 
    2373              :         // by default, notes and dots are not hidden
    2374              :         // this may be changed later to allow unisons to share noteheads
    2375        58930 :         note->setHidden(false);
    2376        58930 :         note->setDotsHidden(false);
    2377              : 
    2378              :         // be sure chord position is initialized
    2379              :         // chord may be moved to the right later
    2380              :         // if there are conflicts between voices
    2381        58930 :         if (!chord->isGrace()) {
    2382        57927 :             chord->mutldata()->setPosX(0.0);
    2383              :         }
    2384              : 
    2385              :         // let user mirror property override the default we calculated
    2386        58930 :         if (note->userMirror() == DirectionH::AUTO) {
    2387        58930 :             mirror = nmirror;
    2388              :         } else {
    2389            0 :             mirror = note->chord()->up();
    2390            0 :             if (note->userMirror() == DirectionH::LEFT) {
    2391            0 :                 mirror = !mirror;
    2392              :             }
    2393              :         }
    2394        58930 :         note->mutldata()->mirror.set_value(mirror);
    2395        58930 :         if (chord->stem()) {
    2396        52072 :             chord->stem()->mutldata()->setPosX(StemLayout::stemPosX(chord));
    2397        52072 :             TLayout::layoutStem(chord->stem(), chord->stem()->mutldata(), ctx.conf()); // needed because mirroring can cause stem position to change
    2398              :         }
    2399              : 
    2400              :         // accumulate return value
    2401        58930 :         if (!mirror) {
    2402        58387 :             const double fretBackground = ctx.conf().styleS(Sid::tabFretPadding).val() * note->spatium();
    2403        58387 :             const double noteWidth = isTab ? note->tabHeadWidth(tab) + 2 * fretBackground : note->bboxRightPos();
    2404        58387 :             maxWidth = std::max(maxWidth, noteWidth);
    2405              :         }
    2406              : 
    2407              :         // prepare for next iteration
    2408        58930 :         prevChord = chord;
    2409        58930 :         prevVisible = note->visible();
    2410        58930 :         prevStaffIdx = staffIdx;
    2411        58930 :         prevTrackIdx = trackIdx;
    2412        58930 :         prevLine = line;
    2413              :     }
    2414              : 
    2415        49760 :     return maxWidth;
    2416              : }
    2417              : 
    2418        50899 : bool ChordLayout::chordHasDotsAllInvisible(Chord* chord)
    2419              : {
    2420        50899 :     if (!chord->dots()) {
    2421        49115 :         return false;
    2422              :     }
    2423              : 
    2424         1788 :     for (Note* note : chord->notes()) {
    2425         1788 :         for (NoteDot* dot : note->dots()) {
    2426         1784 :             if (dot->visible()) {
    2427         1780 :                 return false;
    2428              :             }
    2429              :         }
    2430              :     }
    2431              : 
    2432            4 :     return true;
    2433              : }
    2434              : 
    2435              : //---------------------------------------------------------
    2436              : //   centerX
    2437              : //    return x position for attributes
    2438              : //---------------------------------------------------------
    2439              : 
    2440         7899 : double ChordLayout::centerX(const Chord* chord)
    2441              : {
    2442              :     // TAB 'notes' are always centered on the stem
    2443         7899 :     const Staff* st = chord->staff();
    2444         7899 :     const StaffType* stt = st->staffTypeForElement(chord);
    2445         7899 :     if (stt->isTabStaff()) {
    2446           83 :         return rendering::score::StemLayout::tabStemPosX() * chord->spatium();
    2447              :     }
    2448              : 
    2449         7816 :     const Note* note = chord->up() ? chord->downNote() : chord->upNote();
    2450         7816 :     double x = note->pos().x() + note->noteheadCenterX();
    2451         7816 :     return x;
    2452              : }
    2453              : 
    2454              : //---------------------------------------------------------
    2455              : //   placeDots
    2456              : //---------------------------------------------------------
    2457              : 
    2458        46935 : void ChordLayout::placeDots(const std::vector<Chord*>& chords, const std::vector<Note*>& notes, LayoutContext& ctx)
    2459              : {
    2460        46935 :     Chord* chord = nullptr;
    2461        96033 :     for (Chord* c : chords) {
    2462        49953 :         if (c->dots() > 0) {
    2463          855 :             chord = c;
    2464          855 :             break;
    2465              :         } else {
    2466       107030 :             for (Note* note : c->notes()) {
    2467        57932 :                 ChordLayout::setDotRelativeLine(note, 0, ctx); // this manages the deletion of dots
    2468              :             }
    2469              :         }
    2470              :     }
    2471        46935 :     if (!chord || (chord->staff()->isTabStaff(chord->tick()) && !chord->staff()->staffType(chord->tick())->stemThrough())) {
    2472        46080 :         return;
    2473              :     }
    2474          855 :     std::vector<Note*> topDownNotes;
    2475          855 :     std::vector<Note*> bottomUpNotes;
    2476          855 :     std::vector<int> anchoredDots;
    2477              :     // construct combined chords using the notes from overlapping chords
    2478          855 :     getNoteListForDots(chord, topDownNotes, bottomUpNotes, anchoredDots);
    2479              : 
    2480         1956 :     for (Note* note : notes) {
    2481         1101 :         bool onLine = !(note->line() & 1);
    2482         1101 :         if (onLine) {
    2483          629 :             std::unordered_map<int, Note*> alreadyAdded;
    2484          629 :             bool finished = false;
    2485          644 :             for (Note* otherNote : bottomUpNotes) {
    2486              :                 // Make sure invisible dots have no effect on visible dots, but are still layed out sensibly
    2487           92 :                 if (otherNote->visible() != note->visible()) {
    2488            0 :                     continue;
    2489              :                 }
    2490           92 :                 int dotMove = otherNote->dotPosition() == DirectionV::UP ? -1 : 1;
    2491           92 :                 int otherDotLoc = otherNote->line() + dotMove;
    2492           92 :                 bool added = alreadyAdded.count(otherDotLoc);
    2493           92 :                 if (!added && muse::contains(anchoredDots, otherDotLoc)) {
    2494           13 :                     dotMove = -dotMove; // if the desired space is taken, adjust opposite
    2495           79 :                 } else if (added && alreadyAdded[otherDotLoc] != otherNote) {
    2496            0 :                     dotMove = -dotMove;
    2497              :                 }
    2498              :                 // set y for this note
    2499           92 :                 if (note == otherNote) {
    2500           77 :                     ChordLayout::setDotRelativeLine(note, dotMove, ctx);
    2501           77 :                     finished = true;
    2502           77 :                     anchoredDots.push_back(note->line() + dotMove);
    2503           77 :                     alreadyAdded[otherNote->line() + dotMove] = otherNote;
    2504           77 :                     break;
    2505              :                 }
    2506              :             }
    2507          629 :             if (!finished) {
    2508          552 :                 alreadyAdded.clear();
    2509          592 :                 for (Note* otherNote : topDownNotes) {
    2510          592 :                     if (otherNote->visible() != note->visible()) {
    2511            0 :                         continue;
    2512              :                     }
    2513          592 :                     int dotMove = otherNote->dotPosition() == DirectionV::DOWN ? 1 : -1;
    2514          592 :                     int otherDotLoc = otherNote->line() + dotMove;
    2515          592 :                     bool added = alreadyAdded.count(otherDotLoc);
    2516          592 :                     if (!added && muse::contains(anchoredDots, otherDotLoc)) {
    2517            0 :                         dotMove = -dotMove;
    2518          592 :                     } else if (added && alreadyAdded[otherDotLoc] != otherNote) {
    2519            0 :                         dotMove = -dotMove;
    2520              :                     }
    2521              :                     // set y for this note
    2522          592 :                     if (note == otherNote) {
    2523          552 :                         ChordLayout::setDotRelativeLine(note, dotMove, ctx);
    2524          552 :                         finished = true;
    2525          552 :                         anchoredDots.push_back(note->line() + dotMove);
    2526          552 :                         break;
    2527              :                     }
    2528           40 :                     if (!added) {
    2529           40 :                         alreadyAdded[otherNote->line() + dotMove] = otherNote;
    2530              :                     }
    2531              :                 }
    2532              :             }
    2533          629 :             IF_ASSERT_FAILED(finished) {
    2534              :                 // this should never happen
    2535              :                 // the note is on a line and topDownNotes and bottomUpNotes are all of the lined notes
    2536            0 :                 ChordLayout::setDotRelativeLine(note, 0, ctx);
    2537              :             }
    2538          629 :         } else {
    2539              :             // on a space; usually this means the dot is on this same line, but there is an exception
    2540              :             // for a unison within the same chord.
    2541          588 :             for (Note* otherNote : note->chord()->notes()) {
    2542          588 :                 if (note == otherNote) {
    2543          472 :                     ChordLayout::setDotRelativeLine(note, 0, ctx); // same space as notehead
    2544          472 :                     break;
    2545              :                 }
    2546          116 :                 if (note->line() == otherNote->line()) {
    2547            0 :                     bool adjustDown = (note->chord()->voice() & 1) && !note->chord()->up();
    2548            0 :                     ChordLayout::setDotRelativeLine(note, adjustDown ? 2 : -2, ctx);
    2549            0 :                     break;
    2550              :                 }
    2551              :             }
    2552              :         }
    2553              :     }
    2554          855 : }
    2555              : 
    2556        60062 : void ChordLayout::setDotRelativeLine(Note* note, int dotMove, LayoutContext& ctx)
    2557              : {
    2558        60062 :     double y = dotMove / 2.0;
    2559        60062 :     if (note->staff()->isTabStaff(note->chord()->tick())) {
    2560              :         // with TAB's, dotPosX is not set:
    2561              :         // get dot X from width of fret text and use TAB default spacing
    2562         1356 :         const Staff* st = note->staff();
    2563         1356 :         const StaffType* tab = st->staffTypeForElement(note);
    2564         1356 :         if (tab->stemThrough()) {
    2565              :             // if fret mark on lines, use standard processing
    2566           46 :             if (!tab->onLines()) {
    2567              :                 // if fret marks above lines, raise the dots by half line distance
    2568            0 :                 y = -0.5;
    2569           46 :             } else if (dotMove == 0) {
    2570           45 :                 bool oddVoice = note->voice() & 1;
    2571           45 :                 y = oddVoice ? 0.5 : -0.5;
    2572              :             } else {
    2573            1 :                 y = 0.5;
    2574              :             }
    2575              :         }
    2576              :         // if stems beside staff, do nothing
    2577              :         else {
    2578         1310 :             return;
    2579              :         }
    2580              :     }
    2581        58752 :     y *= note->spatium() * note->staff()->lineDistance(note->tick());
    2582              : 
    2583              :     // apply to dots
    2584              : 
    2585        58752 :     int cdots = static_cast<int>(note->chord()->dots());
    2586        58752 :     int ndots = static_cast<int>(note->dots().size());
    2587              : 
    2588        58752 :     int n = cdots - ndots;
    2589        59411 :     for (int i = 0; i < n; ++i) {
    2590          659 :         NoteDot* dot = Factory::createNoteDot(note);
    2591          659 :         dot->setTrack(note->track());      // needed to know the staff it belongs to (and detect tablature)
    2592          659 :         dot->setVisible(note->visible());
    2593          659 :         ctx.mutDom().undoAddElement(dot);
    2594              :     }
    2595        58752 :     if (n < 0) {
    2596            2 :         for (int i = 0; i < -n; ++i) {
    2597            1 :             ctx.mutDom().undoRemoveElement(note->dots().back());
    2598              :         }
    2599              :     }
    2600              : 
    2601        59797 :     for (NoteDot* dot : note->dots()) {
    2602         1045 :         TLayout::layoutNoteDot(dot, dot->mutldata());
    2603         1045 :         dot->mutldata()->setPosY(y);
    2604              :     }
    2605              : }
    2606              : 
    2607        46935 : void ChordLayout::setDotX(const std::vector<Chord*>& chords, const std::array<double, 3 * VOICES>& dotPos, const Staff* staff,
    2608              :                           const double upDotPosX, const double downDotPosX)
    2609              : {
    2610              :     // Look for conflicts in up-stem and down-stemmed chords. If conflicts, all dots are aligned
    2611              :     // to the same vertical line. If no conflicts, each chords aligns the dots individually.
    2612              :     // Also check for conflicts between similarly stemmed voices where at least one voice is laid out independently
    2613        46935 :     std::set<track_idx_t> combineChordConflicts;
    2614        46935 :     std::set<track_idx_t> separateChordConflicts;
    2615        96942 :     for (Chord* chord1 : chords) {
    2616       106558 :         for (Chord* chord2 : chords) {
    2617        56551 :             if ((chord1 != chord2)
    2618         6544 :                 && ((chord1->up() && !chord2->up() && chord2->upNote()->line() - chord1->downNote()->line() < 2)
    2619         6117 :                     || (!chord1->up() && chord2->up() && chord1->upNote()->line() - chord2->downNote()->line() < 2))
    2620        63095 :                 && Chord::combineVoice(chord1, chord2)) {
    2621          854 :                 combineChordConflicts.insert({ chord1->track(), chord2->track() });
    2622        55697 :             } else if ((chord1 != chord2)
    2623         5690 :                        && ((chord1->up() && chord2->up() && chord1->upNote()->line() - chord2->downNote()->line() < 2)
    2624         5524 :                            || (!chord1->up() && !chord2->up() && chord1->upNote()->line() - chord2->downNote()->line() < 2))
    2625        61387 :                        && !Chord::combineVoice(chord1, chord2)) {
    2626            0 :                 separateChordConflicts.insert({ chord1->track(), chord2->track() });
    2627              :             }
    2628              :         }
    2629              :     }
    2630              : 
    2631        46935 :     const double maxPosX = std::max(upDotPosX, downDotPosX);
    2632        96942 :     for (Chord* chord : chords) {
    2633        50007 :         if (chordHasDotsAllInvisible(chord)) {
    2634            2 :             continue;
    2635              :         }
    2636        50005 :         const bool combineVoices = chord->shouldCombineVoice();
    2637        50005 :         const size_t idx = (VOICES * (chord->staffIdx() - staff->idx() + 1)) + chord->voice();
    2638        50005 :         if (!combineChordConflicts.empty()) {
    2639              :             // There are conflicts
    2640          944 :             if (muse::contains(combineChordConflicts, chord->track())) {
    2641              :                 // In this voice
    2642          836 :                 chord->setDotPosX(maxPosX);
    2643              :             } else {
    2644              :                 // Elsewhere
    2645              :                 // If combining voices, set to max pos, if separating set to own dot pos
    2646          108 :                 chord->setDotPosX(combineVoices ? maxPosX : dotPos.at(idx));
    2647              :             }
    2648              :         } else {
    2649              :             // There are no conflicts
    2650        49061 :             if (combineVoices) {
    2651        49061 :                 if (muse::contains(separateChordConflicts, chord->track())) {
    2652              :                     // Set to own dot pos if this is conflicting with a chord set to lay out independently
    2653            0 :                     chord->setDotPosX(dotPos.at(idx));
    2654              :                 } else {
    2655              :                     // Combine with other voices
    2656        49061 :                     if (chord->up()) {
    2657        25607 :                         chord->setDotPosX(upDotPosX);
    2658              :                     } else {
    2659        23454 :                         chord->setDotPosX(downDotPosX);
    2660              :                     }
    2661              :                 }
    2662              :             } else {
    2663              :                 // Separate
    2664            0 :                 chord->setDotPosX(dotPos.at(idx));
    2665              :             }
    2666              :         }
    2667              :     }
    2668        46935 : }
    2669              : 
    2670              : //---------------------------------------------------------
    2671              : //   layoutChords3
    2672              : //    - calculate positions of dots
    2673              : //---------------------------------------------------------
    2674              : 
    2675        46935 : void ChordLayout::layoutChords3(const std::vector<Chord*>& chords,
    2676              :                                 const std::vector<Note*>& notes, const Staff* staff, LayoutContext& ctx)
    2677              : {
    2678        46935 :     Fraction tick      =  notes.front()->chord()->segment()->tick();
    2679        46935 :     const MStyle& style = ctx.conf().style();
    2680        46935 :     double sp           = staff->spatium(tick);
    2681        46935 :     double stepDistance = sp * staff->lineDistance(tick) * .5;
    2682        46935 :     int stepOffset     = staff->staffType(tick)->stepOffset();
    2683              : 
    2684        46935 :     double upDotPosX    = 0.0;
    2685        46935 :     double downDotPosX  = 0.0;
    2686              :     // Track dot position of voices on this stave and possible cross voices from above & below
    2687        46935 :     std::array<double, 3 * VOICES> dotPos{};
    2688              : 
    2689        46935 :     int nNotes = int(notes.size());
    2690              : 
    2691       105881 :     for (int i = nNotes - 1; i >= 0; --i) {
    2692        58946 :         Note* note     = notes[i];
    2693        58946 :         DirectionV dotPosition = note->userDotPosition();
    2694        58946 :         const Chord* chord = note->chord();
    2695        58946 :         if (chord->dots()) {
    2696          997 :             if (dotPosition == DirectionV::AUTO && nNotes > 1 && note->visible() && !note->dotsHidden()) {
    2697              :                 // resolve dot conflicts
    2698          326 :                 int line = note->line();
    2699          326 :                 Note* above = (i < nNotes - 1) ? notes[i + 1] : nullptr;
    2700          326 :                 if (above
    2701          468 :                     && (!above->visible() || above->dotsHidden() || above->chord()->dots() == 0
    2702          142 :                         || !Chord::combineVoice(chord, above->chord()))) {
    2703           25 :                     above = nullptr;
    2704              :                 }
    2705          326 :                 int intervalAbove = above ? line - above->line() : 1000;
    2706          326 :                 Note* below = (i > 0) ? notes[i - 1] : nullptr;
    2707          326 :                 if (below
    2708          468 :                     && (!below->visible() || below->dotsHidden() || below->chord()->dots() == 0
    2709          142 :                         || !Chord::combineVoice(chord, below->chord()))) {
    2710           77 :                     below = nullptr;
    2711              :                 }
    2712          326 :                 int intervalBelow = below ? below->line() - line : 1000;
    2713          326 :                 if ((line & 1) == 0) {
    2714              :                     // line
    2715          145 :                     if (intervalAbove == 1 && intervalBelow != 1) {
    2716            0 :                         dotPosition = DirectionV::DOWN;
    2717          145 :                     } else if (intervalBelow == 1 && intervalAbove != 1) {
    2718            0 :                         dotPosition = DirectionV::UP;
    2719          145 :                     } else if (intervalAbove == 0 || intervalBelow == 0) {
    2720              :                         // unison
    2721            0 :                         dotPosition = DirectionV::AUTO; // unison conflicts taken care of later
    2722              :                     }
    2723              :                 } else {
    2724              :                     // space
    2725          181 :                     if (intervalAbove == 0 && above->chord()->dots()) {
    2726              :                         // unison
    2727            0 :                         if (!(note->voice() & 1)) {
    2728            0 :                             dotPosition = DirectionV::UP; // space, doesn't matter
    2729              :                         } else {
    2730            0 :                             if (!(above->voice() & 1)) {
    2731            0 :                                 above->setDotPosition(DirectionV::UP);
    2732              :                             } else {
    2733            0 :                                 dotPosition = DirectionV::DOWN;
    2734              :                             }
    2735              :                         }
    2736              :                     }
    2737              :                 }
    2738              :             }
    2739              :         }
    2740        58946 :         if (dotPosition == DirectionV::AUTO) {
    2741        58946 :             dotPosition = note->voice() & 1 ? DirectionV::DOWN : DirectionV::UP;
    2742              :         }
    2743        58946 :         note->setDotPosition(dotPosition);
    2744              :     }
    2745              : 
    2746              :     // Now, we can resolve note conflicts as a superchord
    2747        46935 :     placeDots(chords, notes, ctx);
    2748              : 
    2749              :     // Calculate the chords' dotPosX, and find the leftmost point for accidental layout
    2750        96942 :     for (Chord* chord : chords) {
    2751        50007 :         bool _up = chord->up();
    2752              : 
    2753        50007 :         if (chord->stemSlash()) {
    2754          223 :             TLayout::layoutStemSlash(chord->stemSlash(), chord->stemSlash()->mutldata(), ctx.conf());
    2755              :         }
    2756              : 
    2757              :         double overlapMirror;
    2758        50007 :         Stem* stem = chord->stem();
    2759        50007 :         if (stem) {
    2760        45320 :             overlapMirror = stem->lineWidthMag();
    2761         4687 :         } else if (chord->durationType().headType() == NoteHeadType::HEAD_WHOLE) {
    2762         4193 :             overlapMirror = style.styleAbsolute(Sid::stemWidth) * chord->mag();
    2763              :         } else {
    2764          494 :             overlapMirror = 0.0;
    2765              :         }
    2766              : 
    2767        50007 :         double minDotPosX = 0.0;
    2768              : 
    2769        50007 :         std::vector<Note*> chordNotes = chord->notes();
    2770        50007 :         std::sort(chordNotes.begin(), chordNotes.end(),
    2771         9296 :                   [](Note* n1, const Note* n2) ->bool { return n1->line() < n2->line(); });
    2772       108953 :         for (Note* note : chordNotes) {
    2773        58946 :             double noteX = 0.0;
    2774        58946 :             if (note->ldata()->mirror()) {
    2775          543 :                 if (_up) {
    2776          405 :                     noteX = StemLayout::stemPosX(chord) - overlapMirror;
    2777              :                 } else {
    2778          138 :                     noteX = -note->headBodyWidth() + overlapMirror;
    2779              :                 }
    2780        58403 :             } else if (_up) {
    2781        31434 :                 noteX = chord->noteHeadWidth() - note->headBodyWidth();
    2782              :             }
    2783              : 
    2784        58946 :             double ny = (note->line() + stepOffset) * stepDistance;
    2785        58946 :             if (note->ldata()->pos().y() != ny) {
    2786          457 :                 note->mutldata()->setPosY(ny);
    2787          457 :                 if (stem) {
    2788          182 :                     TLayout::layoutStem(chord->stem(), chord->stem()->mutldata(), ctx.conf());
    2789          182 :                     if (chord->hook()) {
    2790            2 :                         chord->hook()->mutldata()->setPosY(chord->stem()->flagPosition().y());
    2791              :                     }
    2792              :                 }
    2793              :             }
    2794              : 
    2795        58946 :             const double dotX = noteX + note->headBodyWidth() + chord->pos().x();
    2796        58946 :             if (chord->dots()) {
    2797          997 :                 const size_t idx = (VOICES * (chord->staffIdx() - staff->idx() + 1)) + chord->voice();
    2798          997 :                 dotPos.at(idx) = std::max(dotPos.at(idx), dotX);
    2799              :             }
    2800              : 
    2801        58946 :             note->mutldata()->setPosX(noteX);
    2802        58946 :             minDotPosX = std::max(minDotPosX, dotX);
    2803              :         }
    2804              : 
    2805              :         //---------------------------------------------------
    2806              :         //    layout dots simply
    2807              :         //     we will check for conflicts after all the notes have been processed
    2808              :         //---------------------------------------------------
    2809              : 
    2810        50007 :         if (chord->dots()) {
    2811          892 :             if (chordHasDotsAllInvisible(chord)) {
    2812            2 :                 chord->setDotPosX(minDotPosX);
    2813          890 :             } else if (chord->up()) {
    2814          425 :                 upDotPosX = std::max(upDotPosX, minDotPosX);
    2815              :             } else {
    2816          465 :                 downDotPosX = std::max(downDotPosX, minDotPosX);
    2817              :             }
    2818              :         }
    2819        50007 :     }
    2820              : 
    2821        46935 :     setDotX(chords, dotPos, staff, upDotPosX, downDotPosX);
    2822        46935 : }
    2823              : 
    2824        87699 : void ChordLayout::layoutLedgerLines(const std::vector<Chord*>& chords, LayoutContext& ctx)
    2825              : {
    2826       136590 :     for (Chord* item : chords) {
    2827        48891 :         updateLedgerLines(item, ctx);
    2828        49780 :         for (Chord* grace : item->graceNotes()) {
    2829          889 :             updateLedgerLines(grace, ctx);
    2830              :         }
    2831              :     }
    2832        87699 : }
    2833              : 
    2834              : //---------------------------------------------------------
    2835              : //   getNoteListForDots
    2836              : //      This method populates three lists: one for chord notes that need to be checked from the top down,
    2837              : //      one for chords from the bottom up, and one for spaces (where the dot will be in that space)
    2838              : //---------------------------------------------------------
    2839              : 
    2840          855 : void ChordLayout::getNoteListForDots(Chord* c, std::vector<Note*>& topDownNotes, std::vector<Note*>& bottomUpNotes,
    2841              :                                      std::vector<int>& anchoredDots)
    2842              : {
    2843          855 :     Measure* measure = c->measure();
    2844          855 :     bool hasVoices = measure->hasVoices(c->vStaffIdx(), c->tick(), c->ticks(), true);
    2845          855 :     bool hasUpperCrossNotes = false;
    2846          855 :     bool hasLowerCrossNotes = false;
    2847          855 :     const TrackRange partTrackRange = c->part()->trackRange();
    2848          855 :     staff_idx_t partTopStaff = track2staff(partTrackRange.startTrack);
    2849          855 :     staff_idx_t partBottomStaff = track2staff(partTrackRange.endTrack);
    2850          855 :     track_idx_t startVoice = c->track() - c->voice();
    2851              :     // Get the last track we need to check for cross staff notes.
    2852              :     // Either 1 stave away from the stave we are laying out or the bottom staff of the part
    2853          855 :     track_idx_t lastVoice = std::min(c->vStaffIdx() + 2, partBottomStaff) * VOICES;
    2854              : 
    2855              :     // Check for cross staff notes on staff above without dots
    2856          855 :     if (partTopStaff != c->vStaffIdx()) {
    2857          317 :         for (size_t i = partTopStaff * VOICES; i < (partTopStaff + 1) * VOICES; ++i) {
    2858          254 :             if (Chord* voiceChord = measure->findChord(c->tick(), i)) {
    2859           65 :                 if (voiceChord->vStaffIdx() == c->vStaffIdx()) {
    2860            2 :                     hasUpperCrossNotes = true;
    2861            2 :                     startVoice = i;
    2862            2 :                     break;
    2863              :                 }
    2864              :             }
    2865              :         }
    2866              :     }
    2867              : 
    2868              :     // Check for cross staff notes on stave below
    2869          855 :     if (partBottomStaff != c->vStaffIdx()) {
    2870         1475 :         for (size_t i = (c->vStaffIdx() + 1) * VOICES; i < lastVoice; ++i) {
    2871          620 :             if (Chord* voiceChord = measure->findChord(c->tick(), i)) {
    2872          113 :                 if (voiceChord->vStaffIdx() == c->vStaffIdx()) {
    2873            0 :                     hasLowerCrossNotes = true;
    2874            0 :                     break;
    2875              :                 }
    2876              :             }
    2877              :         }
    2878              :     }
    2879              : 
    2880          855 :     if (!hasVoices && !(hasUpperCrossNotes || hasLowerCrossNotes)) {
    2881              :         // only this voice, so topDownNotes is just the notes in the chord
    2882         1445 :         for (Note* note : c->notes()) {
    2883          773 :             if (note->line() & 1) {
    2884          322 :                 int newOffset = 0;
    2885          322 :                 bool adjustDown = (c->voice() & 1) && !c->up();
    2886          322 :                 if (!anchoredDots.empty() && anchoredDots.back() == note->line()) {
    2887            0 :                     if (anchoredDots.size() >= 2 && anchoredDots[anchoredDots.size() - 2] == note->line() + (adjustDown ? 2 : -2)) {
    2888            0 :                         newOffset = adjustDown ? -2 : 2;
    2889              :                     } else {
    2890            0 :                         newOffset = adjustDown ? 2 : -2;
    2891              :                     }
    2892              :                 }
    2893          322 :                 anchoredDots.push_back(note->line() + newOffset);
    2894              :             } else {
    2895          451 :                 topDownNotes.push_back(note);
    2896              :             }
    2897              :         }
    2898          672 :     } else {
    2899              :         // Get a list of notes in this staff that adjust dots from top down,
    2900              :         // bottom up, and also start our locked-in dot list by adding all lines where dots are
    2901              :         // guaranteed
    2902          183 :         lastVoice = hasLowerCrossNotes ? lastVoice : (c->vStaffIdx() + 1) * VOICES;
    2903              :         // Check staves above and below for moved chords (If staff below is available)
    2904          923 :         for (size_t i = startVoice; i < lastVoice; ++i) {
    2905          740 :             if (Chord* voiceChord = measure->findChord(c->tick(), i)) {
    2906              :                 // Skip chords on adjacent staves which have not been moved to this staff
    2907          325 :                 if (voiceChord->vStaffIdx() != c->vStaffIdx()) {
    2908            1 :                     continue;
    2909              :                 }
    2910          324 :                 bool startFromTop = !((voiceChord->voice() & 1) && !voiceChord->up()) && voiceChord->staffMove() != -1;
    2911          324 :                 if (startFromTop) {
    2912          423 :                     for (Note* note : voiceChord->notes()) {
    2913          212 :                         if (note->line() & 1) {
    2914          111 :                             anchoredDots.push_back(note->line());
    2915              :                         } else {
    2916          101 :                             topDownNotes.push_back(note);
    2917              :                         }
    2918              :                     }
    2919              :                 } else {
    2920          229 :                     for (Note* note : voiceChord->notes()) {
    2921          116 :                         if (note->line() & 1) {
    2922           39 :                             anchoredDots.push_back(note->line());
    2923              :                         } else {
    2924           77 :                             bottomUpNotes.push_back(note);
    2925              :                         }
    2926              :                     }
    2927              :                 }
    2928              :             }
    2929              :         }
    2930              :     }
    2931              :     // our two lists now contain only notes that are on lines
    2932          855 :     std::sort(topDownNotes.begin(), topDownNotes.end(),
    2933           35 :               [](Note* n1, Note* n2) { return n1->line() < n2->line(); });
    2934          855 :     std::sort(bottomUpNotes.begin(), bottomUpNotes.end(),
    2935            3 :               [](Note* n1, Note* n2) { return n1->line() > n2->line(); });
    2936          855 : }
    2937              : 
    2938          994 : void ChordLayout::appendGraceNotes(Chord* chord)
    2939              : {
    2940          994 :     Segment* segment = chord->segment();
    2941          994 :     Measure* measure = chord->measure();
    2942          994 :     track_idx_t track = chord->track();
    2943          994 :     staff_idx_t staffIdx = chord->staffIdx();
    2944          994 :     GraceNotesGroup& gnb = chord->graceNotesBefore();
    2945          994 :     GraceNotesGroup& gna = chord->graceNotesAfter();
    2946              : 
    2947              :     //Attach graceNotesBefore of this chord to *this* segment
    2948          994 :     if (!gnb.empty()) {
    2949              :         // If this segment already contains grace notes in the same voice (could happen if a
    2950              :         // previous chord has appended grace-notes-after here) put them in the same vector.
    2951          809 :         EngravingItem* item = segment->preAppendedItem(track);
    2952          809 :         if (item && item->isGraceNotesGroup()) {
    2953           21 :             GraceNotesGroup* gng = toGraceNotesGroup(item);
    2954           21 :             gng->insert(gng->end(), gnb.begin(), gnb.end());
    2955              :         } else {
    2956          788 :             gnb.setAppendedSegment(segment);
    2957          788 :             segment->preAppend(&gnb, track);
    2958              :         }
    2959              :     }
    2960              : 
    2961              :     //Attach graceNotesAfter of this chord to the *following* segment
    2962          994 :     if (!gna.empty()) {
    2963          240 :         Segment* followingSeg = measure->tick2segment(chord->endTick(), SegmentType::All);
    2964          240 :         while (followingSeg
    2965          244 :                && (!followingSeg->hasElements(staff2track(staffIdx), staff2track(staffIdx + 1) - 1) || followingSeg->isTimeTickType())) {
    2966              :             // If there is nothing on this staff, go to next segment
    2967            4 :             followingSeg = followingSeg->next();
    2968              :         }
    2969          240 :         if (followingSeg) {
    2970          240 :             gna.setAppendedSegment(followingSeg);
    2971          240 :             followingSeg->preAppend(&gna, track);
    2972              :         }
    2973              :     }
    2974          994 : }
    2975              : 
    2976              : /* Grace-notes-after have the special property of belonging to
    2977              : *  a segment but being pre-appended to another. This repositioning
    2978              : *  is needed and must be called AFTER horizontal spacing is calculated. */
    2979       116082 : void ChordLayout::repositionGraceNotesAfter(Segment* segment, size_t tracks)
    2980              : {
    2981       791750 :     for (track_idx_t track = 0; track < tracks; track++) {
    2982       675668 :         EngravingItem* item = segment->preAppendedItem(track);
    2983       675668 :         if (!item || !item->isGraceNotesGroup()) {
    2984       674989 :             continue;
    2985              :         }
    2986          679 :         GraceNotesGroup* gng = toGraceNotesGroup(item);
    2987         1585 :         for (Chord* chord : *gng) {
    2988          906 :             double offset = segment->ldata()->pos().x() - chord->layoutParent()->layoutParent()->ldata()->pos().x();
    2989              :             // Difference between the segment they "belong" and the segment they are "appended" to.
    2990          906 :             chord->setPos(chord->ldata()->pos().x() + offset, 0.0);
    2991              :         }
    2992              :     }
    2993       116082 : }
    2994              : 
    2995        32006 : void ChordLayout::clearLineAttachPoints(Measure* measure)
    2996              : {
    2997       132873 :     for (Segment& s : measure->segments()) {
    2998       100867 :         if (!s.isChordRestType()) {
    2999        33973 :             continue;
    3000              :         }
    3001       445370 :         for (EngravingItem* e : s.elist()) {
    3002       378476 :             if (!e || !e->isChord()) {
    3003       326659 :                 continue;
    3004              :             }
    3005        51817 :             Chord* c = toChord(e);
    3006       112819 :             for (Note* n : c->notes()) {
    3007        61002 :                 n->lineAttachPoints().clear();
    3008              :             }
    3009        52727 :             for (Chord* ch : c->graceNotes()) {
    3010         1932 :                 for (Note* n : ch->notes()) {
    3011         1022 :                     n->lineAttachPoints().clear();
    3012              :                 }
    3013              :             }
    3014              :         }
    3015              :     }
    3016        32006 : }
    3017              : 
    3018              : /* We perform a pre-layout of ties and glissandi to obtain the attach points and attach them to
    3019              :  * the notes of the chord. Will be needed for spacing calculation, particularly to
    3020              :  * enforce minTieLength. The true layout of ties and glissandi is done much later. */
    3021        52590 : void ChordLayout::updateLineAttachPoints(Chord* chord, bool isFirstInMeasure, LayoutContext& ctx)
    3022              : {
    3023        52590 :     if (chord->endsNoteAnchoredLine()) {
    3024         1250 :         for (Note* note : chord->notes()) {
    3025         1283 :             for (Spanner* sp : note->spannerBack()) {
    3026          638 :                 if (sp->isGlissando()) {
    3027          220 :                     TLayout::layoutGlissando(toGlissando(sp), ctx);
    3028          418 :                 } else if (sp->isGuitarBend()) {
    3029          415 :                     const StaffType* staffType = chord->staffType();
    3030          415 :                     bool isDiveOnTab = toGuitarBend(sp)->isDive() && staffType && staffType->isTabStaff();
    3031          415 :                     if (!isDiveOnTab) {
    3032          415 :                         TLayout::layoutGuitarBend(toGuitarBend(sp), ctx);
    3033              :                     }
    3034            3 :                 } else if (sp->isNoteLine()) {
    3035            3 :                     TLayout::layoutNoteLine(toNoteLine(sp), ctx);
    3036              :                 }
    3037              :             }
    3038              :         }
    3039              :     }
    3040        52590 :     if (isFirstInMeasure) {
    3041        37001 :         for (Note* note : chord->notes()) {
    3042        20460 :             Tie* tieBack = note->tieBack();
    3043        20460 :             if (tieBack && (note->incomingPartialTie() || tieBack->startNote()->findMeasure() != note->findMeasure())) {
    3044          652 :                 SlurTieLayout::layoutTieBack(tieBack, note->findMeasure()->system(), ctx);
    3045              :             }
    3046              :         }
    3047              :     }
    3048       114476 :     for (Note* note : chord->notes()) {
    3049        61886 :         Tie* tie = note->tieFor();
    3050        61886 :         if (tie) {
    3051         2289 :             if (tie->isPartialTie()) {
    3052           55 :                 SlurTieLayout::layoutTieFor(tie, note->findMeasure()->system());  // line attach points are updated here
    3053              :             }
    3054              : 
    3055         2289 :             Note* endNote = tie->endNote();
    3056         2289 :             if (!endNote) {
    3057           92 :                 continue;
    3058              :             }
    3059         2197 :             const Measure* endNoteMeasure = endNote->findMeasure();
    3060         2197 :             const Measure* noteMeasure = note->findMeasure();
    3061         2197 :             if (endNoteMeasure == noteMeasure || endNoteMeasure->system() != noteMeasure->system()) {
    3062         2197 :                 SlurTieLayout::layoutTieFor(tie, note->findMeasure()->system());  // line attach points are updated here
    3063              :             }
    3064              :         }
    3065              :     }
    3066              : 
    3067        52590 :     SlurTieLayout::layoutLaissezVibChord(chord, ctx);
    3068        52590 : }
    3069              : 
    3070        48219 : void ChordLayout::layoutChordBaseFingering(Chord* chord, System* system, LayoutContext&)
    3071              : {
    3072        48219 :     std::set<staff_idx_t> shapesToRecreate;
    3073        48219 :     std::vector<Note*> notes;
    3074        48219 :     Segment* segment = chord->segment();
    3075        49124 :     for (auto gc : chord->graceNotes()) {
    3076         1922 :         for (auto n : gc->notes()) {
    3077         1017 :             notes.push_back(n);
    3078              :         }
    3079              :     }
    3080       105456 :     for (auto n : chord->notes()) {
    3081        57237 :         notes.push_back(n);
    3082              :     }
    3083        48219 :     std::vector<Fingering*> fingerings;
    3084       106473 :     for (Note* note : notes) {
    3085        58708 :         for (EngravingItem* el : note->el()) {
    3086          454 :             if (el->isFingering()) {
    3087          408 :                 Fingering* f = toFingering(el);
    3088          408 :                 if (f->layoutType() == ElementType::CHORD && !f->isOnCrossBeamSide()) {
    3089              :                     // Fingering on top of cross-staff beams must be laid out later
    3090          370 :                     if (f->placeAbove()) {
    3091          270 :                         fingerings.push_back(f);
    3092              :                     } else {
    3093          100 :                         fingerings.insert(fingerings.begin(), f);
    3094              :                     }
    3095              :                 }
    3096              :             }
    3097              :         }
    3098              :     }
    3099        48589 :     for (Fingering* f : fingerings) {
    3100          370 :         TLayout::layoutFingering(f, f->mutldata());
    3101          370 :         if (f->addToSkyline()) {
    3102          359 :             Note* n = f->note();
    3103              :             RectF r
    3104          359 :                 = f->ldata()->bbox().translated(f->pos() + n->pos() + n->chord()->pos() + segment->pos() + segment->measure()->pos());
    3105          359 :             system->staff(f->note()->chord()->vStaffIdx())->skyline().add(r, f);
    3106              :         }
    3107          370 :         shapesToRecreate.insert(f->staffIdx());
    3108              :     }
    3109        48541 :     for (staff_idx_t staffIdx : shapesToRecreate) {
    3110          322 :         segment->createShape(staffIdx);
    3111              :     }
    3112        48219 : }
    3113              : 
    3114        48116 : void ChordLayout::crossMeasureSetup(Chord* chord, bool on, LayoutContext& ctx)
    3115              : {
    3116        48116 :     if (!on) {
    3117        48106 :         if (chord->crossMeasure() != CrossMeasure::UNKNOWN) {
    3118           10 :             chord->setCrossMeasure(CrossMeasure::UNKNOWN);
    3119           10 :             layoutStem(chord, ctx);
    3120              :         }
    3121        48106 :         return;
    3122              :     }
    3123           10 :     if (chord->crossMeasure() == CrossMeasure::UNKNOWN) {
    3124            5 :         CrossMeasure tempCross = CrossMeasure::NONE;      // assume no cross-measure modification
    3125              :         // if chord has only one note and note is tied forward
    3126            5 :         if (chord->notes().size() == 1 && chord->notes().at(0)->tieFor()) {
    3127            5 :             Chord* tiedChord = chord->notes().at(0)->tieFor()->endNote()->chord();
    3128              :             // if tied note belongs to another measure and to a single-note chord
    3129            5 :             if (tiedChord->measure() != chord->measure() && tiedChord->notes().size() == 1) {
    3130              :                 // get total duration
    3131              :                 std::vector<TDuration> durList = toDurationList(
    3132            5 :                     chord->actualDurationType().fraction()
    3133           10 :                     + tiedChord->actualDurationType().fraction(), true);
    3134              :                 // if duration can be expressed as a single duration
    3135              :                 // apply cross-measure modification
    3136            5 :                 if (durList.size() == 1) {
    3137            5 :                     tempCross = CrossMeasure::FIRST;
    3138            5 :                     chord->setCrossMeasure(tempCross);
    3139            5 :                     chord->setCrossMeasureDurationType(durList[0]);
    3140            5 :                     layoutStem(chord, ctx);
    3141              :                 }
    3142            5 :             }
    3143            5 :             chord->setCrossMeasure(tempCross);
    3144            5 :             tiedChord->setCrossMeasure(tempCross == CrossMeasure::FIRST
    3145              :                                        ? CrossMeasure::SECOND : CrossMeasure::NONE);
    3146              :         }
    3147              :     }
    3148              : }
    3149              : 
    3150              : // called after final position of note is set
    3151        59726 : void ChordLayout::layoutNote2(Note* item, LayoutContext& ctx)
    3152              : {
    3153        59726 :     Chord* chord = item->chord();
    3154        59726 :     const Staff* staff = item->staff();
    3155        59726 :     if (!staff) {
    3156            0 :         return;
    3157              :     }
    3158              : 
    3159        59726 :     Note::LayoutData* ldata = item->mutldata();
    3160        59726 :     const StaffType* staffType = staff->staffTypeForElement(item);
    3161              :     // for standard staves this is done in Score::layoutChords3()
    3162              :     // so that the results are available there
    3163        59726 :     const bool isTabStaff = staffType && staffType->isTabStaff();
    3164              :     // First, for tab staves that have show back-tied fret marks option, we add parentheses to the tied note if
    3165              :     // the tie spans a system boundary. This can't be done in layout as the system of each note is not decided yet
    3166        59726 :     const ShowTiedFret showTiedFret = item->style().value(Sid::tabShowTiedFret).value<ShowTiedFret>();
    3167         1028 :     const bool tieBackParen = isTabStaff && !item->fixed() && item->tieBack()
    3168        60754 :                               && (showTiedFret != ShowTiedFret::TIE_AND_FRET || item->isContinuationOfBend()) && !item->shouldHideFret();
    3169        59726 :     bool useParens =  (tieBackParen || item->ghost()) && !item->hideGeneratedParens();
    3170              : 
    3171        59726 :     if (item->harmonic() && item->displayFret() != Note::DisplayFretOption::NaturalHarmonic) {
    3172           71 :         useParens = false;
    3173              :     }
    3174              : 
    3175        59726 :     if (useParens) {
    3176           91 :         ldata->hasGeneratedParens = true;
    3177        59635 :     } else if (!item->ghost() || item->shouldHideFret() || item->hideGeneratedParens()) {
    3178        59635 :         ldata->hasGeneratedParens = false;
    3179              :     }
    3180              : 
    3181        59726 :     int dots = chord->dots();
    3182        59726 :     if (dots && !item->dots().empty()) {
    3183          997 :         if (chord->slash() && !item->visible()) {
    3184            0 :             item->setDotsHidden(true);
    3185              :         }
    3186              :         // if chords have notes with different mag, dots must still  align
    3187          997 :         double correctMag = chord->notes().size() > 1 ? chord->mag() : item->mag();
    3188          997 :         double d  = ctx.conf().point(ctx.conf().styleS(Sid::dotNoteDistance)) * correctMag;
    3189          997 :         double dd = ctx.conf().point(ctx.conf().styleS(Sid::dotDotDistance)) * correctMag;
    3190          997 :         double x  = isTabStaff ? chord->dotPosX() - item->pos().x() : chord->dotPosX() - item->pos().x() - chord->pos().x();
    3191              :         // in case of dots with different size, center-align them
    3192          997 :         if (item->mag() != chord->mag() && chord->notes().size() > 1) {
    3193            4 :             double relativeMag = item->mag() / chord->mag();
    3194            4 :             double centerAlignOffset = item->dot(0)->width() * (1 / relativeMag - 1) / 2;
    3195            4 :             x += centerAlignOffset;
    3196              :         }
    3197              :         // adjust dot distance for hooks
    3198          997 :         Hook* hook = chord->hook();
    3199          997 :         if (chord->up() && hook && hook->visible()) {
    3200           56 :             double hookRight = hook->width() + hook->x() + chord->pos().x();
    3201           56 :             double hookBottom = hook->height() + hook->y() + chord->pos().y()
    3202           56 :                                 + (0.25 * item->spatium());
    3203              :             // the top dot in the chord, not the dot for this particular note:
    3204           56 :             double dotY = chord->notes().back()->y() + chord->notes().back()->dots().front()->pos().y();
    3205           56 :             if (chord->dotPosX() < hookRight && dotY < hookBottom) {
    3206           56 :                 d = hook->width();
    3207              :             }
    3208              :         }
    3209              :         // if TAB and stems through staff
    3210          997 :         if (isTabStaff && staffType->stemThrough()) {
    3211              :             // with TAB's, dot Y is not calculated during layoutChords3(),
    3212              :             // as layoutChords3() is not even called for TAB's;
    3213              :             // setDotRelativeLine() actually also manages creation/deletion of NoteDot's
    3214            1 :             ChordLayout::setDotRelativeLine(item, 0, ctx);
    3215              : 
    3216              :             // use TAB default note-to-dot spacing
    3217            1 :             dd = STAFFTYPE_TAB_DEFAULTDOTDIST_X.toAbsolute(item->spatium());
    3218            1 :             d = dd * 0.5;
    3219              :         }
    3220              : 
    3221              :         // apply to dots
    3222              :         // Invisible dots are laid out separately,
    3223              :         // with a distance of 0.1sp to distinguish from visible ones
    3224          997 :         double visibleX = x + d;
    3225          997 :         double invisibleX = x;
    3226              : 
    3227         2040 :         for (NoteDot* dot : item->dots()) {
    3228         1043 :             if (dot->visible()) {
    3229         1041 :                 dot->mutldata()->setPosX(visibleX);
    3230         1041 :                 visibleX += dd;
    3231              :             } else {
    3232            2 :                 invisibleX += (0.1 * item->spatium());
    3233            2 :                 dot->mutldata()->setPosX(invisibleX);
    3234            2 :                 invisibleX += item->symWidth(SymId::augmentationDot) * dot->mag();
    3235              :             }
    3236              :         }
    3237              :     }
    3238              : 
    3239              :     // layout elements attached to note
    3240        60181 :     for (EngravingItem* e : item->el()) {
    3241          455 :         if (e->isSymbol()) {
    3242           27 :             e->mutldata()->setMag(item->mag());
    3243           27 :             TLayout::layoutItem(e, ctx);
    3244          428 :         } else if (e->isFingering()) {
    3245              :             // don't set mag; fingerings should not scale with note
    3246          409 :             Fingering* f = toFingering(e);
    3247          409 :             if (f->propertyFlags(Pid::PLACEMENT) == PropertyFlags::STYLED) {
    3248          363 :                 f->setPlacement(f->calculatePlacement());
    3249              :             }
    3250              :             // layout fingerings that are placed relative to notehead
    3251              :             // fingerings placed relative to chord will be laid out later
    3252          409 :             if (f->layoutType() == ElementType::NOTE) {
    3253           13 :                 TLayout::layoutFingering(f, f->mutldata());
    3254              :             }
    3255              :         } else {
    3256           19 :             e->mutldata()->setMag(item->mag());
    3257           19 :             TLayout::layoutItem(e, ctx);
    3258              :         }
    3259              :     }
    3260              : 
    3261        59726 :     TLayout::fillNoteShape(item, ldata);
    3262              : }
    3263              : 
    3264        50677 : void ChordLayout::createParenGroups(Chord* chord)
    3265              : {
    3266        50677 :     std::vector<Note*> addParens;
    3267        50677 :     std::vector<Note*> removeParens;
    3268              : 
    3269       110403 :     for (Note* note : chord->notes()) {
    3270        59726 :         const NoteParenthesisInfo* noteParenInfo = note->parenthesisInfo();
    3271        59726 :         const Parenthesis* leftParen = noteParenInfo ? noteParenInfo->leftParen() : nullptr;
    3272        59726 :         bool parenGenerated = leftParen && leftParen->generated();
    3273              : 
    3274        59726 :         if (note->ldata()->hasGeneratedParens()) {
    3275           91 :             if (noteParenInfo) {
    3276           24 :                 if (parenGenerated) {
    3277           18 :                     EditChord::removeChordParentheses(chord, { note }, false, true);
    3278              :                 } else {
    3279           18 :                     continue;
    3280              :                 }
    3281              :             }
    3282           73 :             addParens.push_back(note);
    3283           73 :             note->undoChangeProperty(Pid::HAS_PARENTHESES, ParenthesesMode::BOTH);
    3284        59635 :         } else if (parenGenerated) {
    3285            0 :             removeParens.push_back(note);
    3286              :         }
    3287              :     }
    3288              : 
    3289        50677 :     EditChord::addChordParentheses(chord, addParens, false, true);
    3290        50677 :     EditChord::removeChordParentheses(chord, removeParens, false, true);
    3291        50677 : }
    3292              : 
    3293        51681 : void ChordLayout::checkStartEndSlurs(Chord* chord, LayoutContext& ctx)
    3294              : {
    3295        51681 :     chord->startEndSlurs().reset();
    3296        53545 :     for (Spanner* spanner : chord->startingSpanners()) {
    3297         1864 :         if (!spanner->isSlur()) {
    3298         1024 :             continue;
    3299              :         }
    3300          840 :         Slur* slur = toSlur(spanner);
    3301          840 :         SlurTieLayout::computeUp(slur, ctx);
    3302          840 :         if (slur->up()) {
    3303          372 :             chord->startEndSlurs().startUp = true;
    3304              :         } else {
    3305          468 :             chord->startEndSlurs().startDown = true;
    3306              :         }
    3307              :         // Check if end chord has been connected to this slur. If not, connect it.
    3308          840 :         if (!slur->endChord()) {
    3309           15 :             continue;
    3310              :         }
    3311          825 :         slur->endChord()->addEndingSpanner(slur);
    3312              :     }
    3313        53663 :     for (Spanner* spanner : chord->endingSpanners()) {
    3314         1982 :         if (!spanner->isSlur()) {
    3315          947 :             continue;
    3316              :         }
    3317         1035 :         if (toSlur(spanner)->up()) {
    3318          483 :             chord->startEndSlurs().endUp = true;
    3319              :         } else {
    3320          552 :             chord->startEndSlurs().endDown = true;
    3321              :         }
    3322              :     }
    3323        51681 : }
    3324              : 
    3325       443929 : void ChordLayout::checkAndFillShape(const ChordRest* item, ChordRest::LayoutData* ldata, const LayoutConfiguration& conf)
    3326              : {
    3327              : #ifdef MUE_ENABLE_ENGRAVING_LD_ACCESS
    3328              :     Shape origin = ldata->shape(LD_ACCESS::PASS);
    3329              : #endif
    3330              : 
    3331       443929 :     fillShape(item, ldata, conf);
    3332              : 
    3333              : #ifdef MUE_ENABLE_ENGRAVING_LD_ACCESS
    3334              :     Shape fixed = ldata->shape(LD_ACCESS::PASS);
    3335              :     if (!origin.equal(fixed)) {
    3336              :         LOGE() << "Shape not actual for item: " << item->typeName();
    3337              :     }
    3338              : #endif
    3339       443929 : }
    3340              : 
    3341       495359 : void ChordLayout::fillShape(const ChordRest* item, Chord::LayoutData* ldata, const LayoutConfiguration& conf)
    3342              : {
    3343       495359 :     switch (item->type()) {
    3344       254761 :     case ElementType::CHORD:
    3345       254761 :         fillShape(toChord(item), static_cast<Chord::LayoutData*>(ldata));
    3346       254761 :         break;
    3347          917 :     case ElementType::MEASURE_REPEAT:
    3348          917 :         fillShape(toMeasureRepeat(item), static_cast<MeasureRepeat::LayoutData*>(ldata), conf);
    3349          917 :         break;
    3350       239681 :     default:
    3351       239681 :         RestLayout::fillShape(toRest(item), static_cast<Rest::LayoutData*>(ldata), conf);
    3352       239681 :         break;
    3353              :     }
    3354       495359 : }
    3355              : 
    3356       552864 : Shape ChordLayout::chordRestShape(const ChordRest* item)
    3357              : {
    3358       552864 :     Shape shape;
    3359              :     {
    3360       564909 :         for (Lyrics* l : item->lyrics()) {
    3361        12045 :             if (!l || !l->addToSkyline() || l->xmlText().empty()) {
    3362          207 :                 continue;
    3363              :             }
    3364        11838 :             RectF bbox = l->ldata()->bbox().translated(l->pos());
    3365        11838 :             shape.addHorizontalSpacing(l, bbox.left(), bbox.right());
    3366              :         }
    3367              :     }
    3368              : 
    3369       552864 :     return shape;
    3370            0 : }
    3371              : 
    3372         1339 : bool ChordLayout::leaveSpaceForTie(const Articulation* item)
    3373              : {
    3374         1339 :     if (!item->ownershipParent() || !item->ownershipParent()->isChord()) {
    3375            0 :         return false;
    3376              :     }
    3377              : 
    3378         1339 :     Chord* chord = toChord(item->chordRest());
    3379         1339 :     bool up = item->ldata()->up;
    3380         1339 :     Note* note = up ? chord->upNote() : chord->downNote();
    3381         1339 :     Tie* tieFor = note->tieFor();
    3382         1339 :     Tie* tieBack = note->tieBack();
    3383              : 
    3384         1339 :     if (!tieFor && !tieBack) {
    3385         1329 :         return false;
    3386              :     }
    3387              : 
    3388            4 :     bool leaveSpace = (tieFor && tieFor->up() == up && tieFor->isOuterTieOfChord(Grip::START))
    3389           14 :                       || (tieBack && tieBack->up() == up && tieBack->isOuterTieOfChord(Grip::END));
    3390              : 
    3391           10 :     return leaveSpace;
    3392              : }
    3393              : 
    3394       254785 : void ChordLayout::fillShape(const Chord* item, ChordRest::LayoutData* ldata)
    3395              : {
    3396       254785 :     Shape shape(Shape::Type::Composite);
    3397              : 
    3398       254785 :     Hook* hook = item->hook();
    3399       254785 :     if (hook) {
    3400        10853 :         LD_CONDITION(hook->ldata()->isSetShape());
    3401              :     }
    3402              : 
    3403       254785 :     Stem* stem = item->stem();
    3404       254785 :     if (stem) {
    3405       228324 :         LD_CONDITION(stem->ldata()->isSetShape());
    3406              :     }
    3407              : 
    3408       254785 :     StemSlash* stemSlash = item->stemSlash();
    3409       254785 :     if (stemSlash) {
    3410          577 :         LD_CONDITION(stemSlash->ldata()->isSetShape());
    3411              :     }
    3412              : 
    3413       254785 :     TremoloSingleChord* tremolo = item->tremoloSingleChord();
    3414       254785 :     if (tremolo) {
    3415         1371 :         LD_CONDITION(tremolo->ldata()->isSetShape());
    3416              :     }
    3417              : 
    3418       254785 :     Arpeggio* arpeggio = item->arpeggio();
    3419       254785 :     if (arpeggio) {
    3420         1800 :         LD_CONDITION(arpeggio->ldata()->isSetShape());
    3421              :     }
    3422              : 
    3423       254785 :     Arpeggio* spanArpeggio = item->spanArpeggio();
    3424       254785 :     if (spanArpeggio) {
    3425         2354 :         LD_CONDITION(spanArpeggio->ldata()->isSetShape());
    3426              :     }
    3427              : 
    3428       254785 :     BeamSegment* beamlet = item->beamlet();
    3429              : 
    3430       254785 :     if (hook && hook->addToSkyline()) {
    3431        10831 :         shape.add(hook->shape().translate(hook->pos()));
    3432              :     }
    3433              : 
    3434       254785 :     if (stem && stem->addToSkyline()) {
    3435       228091 :         shape.add(stem->shape().translate(stem->pos()));
    3436              :     }
    3437              : 
    3438       254785 :     if (stemSlash && stemSlash->addToSkyline()) {
    3439          577 :         shape.add(stemSlash->shape().translate(stemSlash->pos()));
    3440              :     }
    3441              : 
    3442       254785 :     if (tremolo && tremolo->addToSkyline()) {
    3443         1371 :         shape.add(tremolo->shape().translate(tremolo->pos()));
    3444              :     }
    3445              : 
    3446       254785 :     if (arpeggio && arpeggio->addToSkyline()) {
    3447         1786 :         shape.add(arpeggio->shape().translate(arpeggio->pos()));
    3448              :     }
    3449              : 
    3450       254785 :     if (spanArpeggio && !arpeggio && spanArpeggio->vStaffIdx() != item->vStaffIdx() && spanArpeggio->addToSkyline()) {
    3451          188 :         PointF spanArpPos = spanArpeggio->pos() - (item->pagePos() - spanArpeggio->chord()->pagePos());
    3452          188 :         shape.add(spanArpeggio->shape().translate(spanArpPos));
    3453              :     }
    3454              : 
    3455       556620 :     for (Note* note : item->notes()) {
    3456       301835 :         shape.add(note->shape().translate(note->pos()));
    3457              :     }
    3458              : 
    3459       254785 :     if (item->tabDur() && item->tabDur()->addToSkyline()) {
    3460          144 :         shape.add(item->tabDur()->shape().translate(item->tabDur()->pos()));
    3461              :     }
    3462              : 
    3463       256020 :     for (const NoteParenthesisInfo* parenInfo : item->noteParentheses()) {
    3464         1235 :         Parenthesis* leftParen = parenInfo->leftParen();
    3465         1235 :         Parenthesis* rightParen = parenInfo->rightParen();
    3466              : 
    3467         1235 :         if (leftParen && leftParen->addToSkyline()) {
    3468         1214 :             shape.add(leftParen->shape().translate(leftParen->pos()));
    3469              :         }
    3470         1235 :         if (rightParen && rightParen->addToSkyline()) {
    3471         1214 :             shape.add(rightParen->shape().translate(rightParen->pos()));
    3472              :         }
    3473              :     }
    3474              : 
    3475       255816 :     for (EngravingItem* e : item->el()) {
    3476         1031 :         if (e->addToSkyline()) {
    3477         1031 :             shape.add(e->shape().translate(e->pos()));
    3478              :         }
    3479              :     }
    3480              : 
    3481       254785 :     shape.add(chordRestShape(item));      // add lyrics
    3482              : 
    3483       719113 :     for (const LedgerLine* l : item->ledgerLines()) {
    3484       464328 :         shape.add(l->shape().translate(l->pos()));
    3485              :     }
    3486              : 
    3487       254785 :     if (beamlet && stem) {
    3488         1653 :         double xPos = beamlet->line.p1().x() - stem->ldata()->pos().x();
    3489         1653 :         if (beamlet->isBefore && !item->up()) {
    3490          712 :             xPos -= stem->width();
    3491          941 :         } else if (!beamlet->isBefore && item->up()) {
    3492          192 :             xPos += stem->width();
    3493              :         }
    3494         1653 :         shape.add(beamlet->shape().translate(PointF(-xPos, 0.0)));
    3495              :     }
    3496              : 
    3497       263448 :     for (const Articulation* a : item->articulations()) {
    3498              :         // Only add lv to shape
    3499         8663 :         if (a->isLaissezVib()) {
    3500            0 :             shape.add(a->shape().translate(a->pos()));
    3501              :         }
    3502              :     }
    3503              : 
    3504       254785 :     const Parenthesis* leftParen = item->leftParen();
    3505       254785 :     if (leftParen && leftParen->addToSkyline()) {
    3506            0 :         shape.add(leftParen->ldata()->shape().translated(leftParen->pos()));
    3507              :     }
    3508       254785 :     const Parenthesis* rightParen = item->rightParen();
    3509       254785 :     if (rightParen && rightParen->addToSkyline()) {
    3510            0 :         shape.add(rightParen->ldata()->shape().translated(rightParen->pos()));
    3511              :     }
    3512              : 
    3513       254785 :     ldata->setShape(shape);
    3514       254785 : }
    3515              : 
    3516         1282 : void ChordLayout::fillShape(const MeasureRepeat* item, MeasureRepeat::LayoutData* ldata, const LayoutConfiguration&)
    3517              : {
    3518         1282 :     Shape shape(Shape::Type::Composite);
    3519              : 
    3520         1282 :     shape.add(item->numberRect(), item);
    3521         1282 :     shape.add(item->symBbox(ldata->symId), item);
    3522              : 
    3523         1282 :     ldata->setShape(shape);
    3524         1282 : }
    3525              : 
    3526          460 : void ChordLayout::addLineAttachPoints(Spanner* spanner)
    3527              : {
    3528          460 :     assert(spanner->anchor() == Spanner::Anchor::NOTE);
    3529              : 
    3530          460 :     const SpannerSegment* frontSeg = toSpannerSegment(spanner->frontSegment());
    3531          460 :     const SpannerSegment* backSeg = toSpannerSegment(spanner->backSegment());
    3532          460 :     Note* startNote = nullptr;
    3533          460 :     Note* endNote = nullptr;
    3534              : 
    3535          460 :     EngravingItem* startElement = spanner->startElement();
    3536          460 :     EngravingItem* endElement = spanner->endElement();
    3537          460 :     if (startElement && startElement->isNote()) {
    3538          460 :         startNote = toNote(startElement);
    3539              :     }
    3540          460 :     if (endElement && endElement->isNote()) {
    3541          460 :         endNote = toNote(endElement);
    3542              :     }
    3543          460 :     if (!frontSeg || !backSeg || !startNote || !endNote) {
    3544            0 :         return;
    3545              :     }
    3546          460 :     double startX = frontSeg->ldata()->pos().x();
    3547          460 :     double endX = backSeg->pos2().x() + backSeg->ldata()->pos().x(); // because pos2 is relative to ipos
    3548              :     // Here we don't pass y() because its value is unreliable during the first stages of layout.
    3549              :     // The y() is irrelevant anyway for horizontal spacing.
    3550          460 :     startNote->addStartLineAttachPoint(PointF(startX, 0.0), spanner);
    3551          460 :     endNote->addEndLineAttachPoint(PointF(endX, 0.0), spanner);
    3552              : }
        

Generated by: LCOV version 2.0-1