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 : }
|