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 "slurtielayout.h"
23 :
24 : #include "iengravingconfiguration.h" // IWYU pragma: keep
25 : #include "iengravingfont.h"
26 :
27 : #include "dom/slur.h"
28 : #include "dom/chord.h"
29 : #include "dom/clef.h"
30 : #include "dom/score.h"
31 : #include "dom/system.h"
32 : #include "dom/staff.h"
33 : #include "dom/stafftype.h"
34 : #include "dom/ledgerline.h"
35 : #include "dom/note.h"
36 : #include "dom/hook.h"
37 : #include "dom/stem.h"
38 : #include "dom/tremolotwochord.h"
39 : #include "dom/tie.h"
40 : #include "dom/engravingitem.h"
41 : #include "dom/measure.h"
42 : #include "dom/guitarbend.h"
43 : #include "dom/laissezvib.h"
44 : #include "dom/parenthesis.h"
45 : #include "dom/partialtie.h"
46 :
47 : #include "editing/editchord.h"
48 :
49 : #include "tlayout.h"
50 : #include "chordlayout.h"
51 : #include "stemlayout.h"
52 : #include "tremololayout.h"
53 :
54 : #include "draw/types/transform.h"
55 : #include "global/stringutils.h"
56 :
57 : using namespace muse::draw;
58 : using namespace mu::engraving;
59 : using namespace mu::engraving::rendering::score;
60 :
61 929 : SpannerSegment* SlurTieLayout::layoutSystem(Slur* item, System* system, LayoutContext& ctx)
62 : {
63 929 : const double horizontalTieClearance = 0.35 * item->spatium();
64 929 : const double tieClearance = 0.65 * item->spatium();
65 929 : const double continuedSlurOffsetY = item->spatium() * .4;
66 929 : const double continuedSlurMaxDiff = 2.5 * item->spatium();
67 929 : Fraction stick = system->firstMeasure()->tick();
68 929 : Fraction etick = system->lastMeasure()->endTick();
69 :
70 929 : SlurSegment* slurSegment = toSlurSegment(TLayout::getNextLayoutSystemSegment(item, system, [item]() {
71 471 : return item->newSlurTieSegment();
72 : }));
73 :
74 : SpannerSegmentType sst;
75 929 : if (item->tick() >= stick) {
76 : //
77 : // this is the first call to layoutSystem,
78 : // processing the first line segment
79 : //
80 911 : if (item->track2() == muse::nidx) {
81 8 : item->setTrack2(item->track());
82 : }
83 911 : if (item->startCR() == 0 || item->startCR()->measure() == 0) {
84 0 : LOGD("Slur::layout(): track %zu-%zu %p - %p tick %d-%d null start anchor",
85 0 : item->track(), item->track2(), item->startCR(), item->endCR(), item->tick().ticks(), item->tick2().ticks());
86 0 : return slurSegment;
87 : }
88 1000 : if (item->ticks().isZero() && !(item->startCR() && item->startCR()->isGrace()) && !(item->endCR() && item->endCR()->isGrace())
89 1000 : && !item->isIncoming() && !item->isOutgoing() && !item->isTappingHalfSlur()) {
90 0 : LOGD("Slur::layout(): track %zu tick %d zero-length slur, skipping",
91 0 : item->track(), item->tick().ticks());
92 0 : return slurSegment;
93 : }
94 911 : if (item->endCR() == 0) { // sanity check
95 0 : item->setEndElement(item->startCR());
96 0 : item->setTick2(item->tick());
97 : }
98 911 : computeUp(item, ctx);
99 911 : sst = item->tick2() < etick ? SpannerSegmentType::SINGLE : SpannerSegmentType::BEGIN;
100 18 : } else if (item->tick() < stick && item->tick2() >= etick) {
101 0 : sst = SpannerSegmentType::MIDDLE;
102 : } else {
103 18 : sst = SpannerSegmentType::END;
104 : }
105 929 : slurSegment->setSpannerSegmentType(sst);
106 :
107 929 : SlurTiePos sPos;
108 929 : slurPos(item, &sPos, ctx);
109 929 : PointF p1, p2;
110 : // adjust for ties
111 929 : p1 = sPos.p1;
112 929 : p2 = sPos.p2;
113 929 : bool constrainLeftAnchor = false;
114 :
115 929 : const bool incomingPartialSlur = item->isIncoming();
116 929 : const bool outgoingPartialSlur = item->isOutgoing();
117 :
118 : // start anchor, either on the start chordrest or at the beginning of the system
119 929 : if ((sst == SpannerSegmentType::SINGLE || sst == SpannerSegmentType::BEGIN) && !incomingPartialSlur) {
120 887 : Chord* sc = item->startCR()->isChord() ? toChord(item->startCR()) : nullptr;
121 :
122 : // on chord
123 887 : if (sc) {
124 887 : Tie* tie = (item->up() ? sc->upNote() : sc->downNote())->tieFor();
125 887 : PointF endPoint = PointF();
126 887 : if (tie && (tie->isInside() || tie->up() != item->up() || tieSegmentShouldBeSkipped(tie))) {
127 : // there is a tie that starts on this chordrest
128 4 : tie = nullptr;
129 : }
130 887 : if (tie && !tie->segmentsEmpty()) {
131 7 : endPoint = tie->segmentAt(0)->ups(Grip::START).pos();
132 : }
133 887 : bool adjustedVertically = false;
134 887 : if (tie) {
135 7 : if (item->up() && tie->up()) {
136 0 : if (endPoint.y() - p1.y() < tieClearance) {
137 0 : p1.ry() = endPoint.y() - tieClearance;
138 0 : adjustedVertically = true;
139 : }
140 7 : } else if (!item->up() && !tie->up()) {
141 7 : if (p1.y() - endPoint.y() < tieClearance) {
142 7 : p1.ry() = endPoint.y() + tieClearance;
143 7 : adjustedVertically = true;
144 : }
145 : }
146 : }
147 887 : Tie* tieBack = sc->upNote()->tieBack();
148 880 : if (!adjustedVertically && tieBack && (!tieBack->isInside() || tieBack->isPartialTie())
149 1767 : && tieBack->up() == item->up() && !tieSegmentShouldBeSkipped(tieBack)) {
150 : // there is a tie that ends on this chordrest
151 7 : tie = tieBack;
152 7 : if (!tie->segmentsEmpty()) {
153 7 : endPoint = tie->segmentAt(static_cast<int>(tie->nsegments()) - 1)->ups(Grip::END).pos();
154 7 : if (std::abs(endPoint.y() - p1.y()) < tieClearance) {
155 6 : p1.rx() += horizontalTieClearance;
156 : }
157 : }
158 : }
159 : }
160 929 : } else if (sst == SpannerSegmentType::END || sst == SpannerSegmentType::MIDDLE || incomingPartialSlur) {
161 : // beginning of system
162 42 : Measure* measure = item->startCR()->measure();
163 42 : ChordRest* firstCr = incomingPartialSlur ? measure->firstChordRest(item->track()) : system->firstChordRest(item->track2());
164 42 : const bool sameStaff = firstCr && firstCr->vStaffIdx() == slurSegment->vStaffIdx();
165 42 : double y = p1.y();
166 42 : if (firstCr && firstCr == item->endCR()) {
167 21 : constrainLeftAnchor = true;
168 : }
169 42 : if (firstCr && firstCr->isChord() && sameStaff) {
170 41 : Chord* chord = toChord(firstCr);
171 41 : Shape chordShape = chord->shape();
172 82 : chordShape.removeTypes({ ElementType::ACCIDENTAL });
173 41 : y = item->up() ? chordShape.top() : chordShape.bottom();
174 41 : bool isAboveStem = chord->stem() && chord->up() == item->up();
175 41 : if (!isAboveStem) {
176 40 : y += continuedSlurOffsetY * (item->up() ? -1 : 1);
177 : }
178 41 : }
179 :
180 42 : double segmentX = incomingPartialSlur ? measure->firstNoteRestSegmentX(true) : system->firstNoteRestSegmentX(true);
181 42 : p1 = PointF(segmentX, y);
182 :
183 : // adjust for ties at the start of the system
184 42 : ChordRest* cr = incomingPartialSlur ? measure->firstChordRest(item->track()) : system->firstChordRest(item->track());
185 42 : const bool tieSameStaff = cr && cr->vStaffIdx() == item->staffIdx();
186 42 : if (cr && cr->isChord() && cr->tick() >= stick && cr->tick() <= etick && tieSameStaff) {
187 41 : Chord* c = toChord(cr);
188 41 : Tie* tie = nullptr;
189 41 : PointF endPoint;
190 41 : Tie* tieBack = c->upNote()->tieBack();
191 24 : if (tieBack && (tieBack->isPartialTie() || !tieBack->isInside()) && tieBack->up() == item->up()
192 65 : && !tieSegmentShouldBeSkipped(tieBack)) {
193 : // there is a tie that ends on this chordrest
194 24 : if (!tieBack->segmentsEmpty()) { //Checks for spanner segment esxists
195 24 : tie = tieBack;
196 24 : endPoint = tie->backSegment()->ups(Grip::START).pos();
197 : }
198 : }
199 41 : if (tie) {
200 24 : if (tie->isPartialTie()) {
201 24 : p1.rx() = endPoint.rx();
202 : }
203 24 : if (item->up() && tie->up()) {
204 2 : if (endPoint.y() - p1.y() < tieClearance) {
205 2 : p1.ry() = endPoint.y() - tieClearance;
206 : }
207 22 : } else if (!item->up() && !tie->up()) {
208 22 : if (p1.y() - endPoint.y() < tieClearance) {
209 21 : p1.ry() = endPoint.y() + tieClearance;
210 : }
211 : }
212 : }
213 : }
214 : }
215 :
216 : // end anchor
217 929 : if ((sst == SpannerSegmentType::SINGLE || sst == SpannerSegmentType::END) && !outgoingPartialSlur) {
218 882 : Chord* ec = item->endCR()->isChord() ? toChord(item->endCR()) : nullptr;
219 :
220 : // on chord
221 882 : if (ec) {
222 869 : Tie* tie = (item->up() ? ec->upNote() : ec->downNote())->tieBack();
223 869 : PointF endPoint;
224 869 : if (tie && (tie->isInside() || tie->up() != item->up() || tieSegmentShouldBeSkipped(tie))) {
225 4 : tie = nullptr;
226 : }
227 869 : bool adjustedVertically = false;
228 869 : if (tie && !tie->segmentsEmpty()) {
229 27 : endPoint = tie->segmentAt(0)->ups(Grip::END).pos();
230 27 : if (item->up() && tie->up()) {
231 0 : if (endPoint.y() - p2.y() < tieClearance) {
232 0 : p2.ry() = endPoint.y() - tieClearance;
233 0 : adjustedVertically = true;
234 : }
235 27 : } else if (!item->up() && !tie->up()) {
236 27 : if (p2.y() - endPoint.y() < tieClearance) {
237 27 : p2.ry() = endPoint.y() + tieClearance;
238 27 : adjustedVertically = true;
239 : }
240 : }
241 : }
242 869 : Tie* tieFor = ec->upNote()->tieFor();
243 842 : if (!adjustedVertically && tieFor && (tieFor->isPartialTie() || !tieFor->isInside()) && tieFor->up() == item->up()
244 1711 : && !tieSegmentShouldBeSkipped(tieFor)) {
245 : // there is a tie that starts on this chordrest
246 6 : if (!tieFor->segmentsEmpty() && std::abs(tieFor->frontSegment()->ups(Grip::START).pos().y() - p2.y()) < tieClearance) {
247 6 : p2.rx() -= horizontalTieClearance;
248 : }
249 : }
250 : }
251 882 : } else {
252 : // at end of system
253 47 : Measure* measure = item->endCR()->measure();
254 47 : ChordRest* lastCr = outgoingPartialSlur ? measure->lastChordRest(item->track()) : system->lastChordRest(item->track());
255 47 : double y = p1.y();
256 47 : const bool sameStaff = lastCr && lastCr->vStaffIdx() == slurSegment->vStaffIdx();
257 47 : if (lastCr && lastCr == item->startCR()) {
258 2 : y += 0.25 * item->spatium() * (item->up() ? -1 : 1);
259 45 : } else if (lastCr && lastCr->isChord() && sameStaff) {
260 38 : Chord* chord = toChord(lastCr);
261 38 : Shape chordShape = chord->shape();
262 76 : chordShape.removeTypes({ ElementType::ACCIDENTAL });
263 38 : y = item->up() ? chordShape.top() : chordShape.bottom();
264 38 : bool isAboveStem = chord->stem() && chord->up() == item->up();
265 38 : if (!isAboveStem) {
266 38 : y += continuedSlurOffsetY * (item->up() ? -1 : 1);
267 : }
268 38 : double diff = item->up() ? y - p1.y() : p1.y() - y;
269 38 : if (diff > continuedSlurMaxDiff) {
270 0 : y = p1.y() + (y > p1.y() ? continuedSlurMaxDiff : -continuedSlurMaxDiff);
271 : }
272 38 : }
273 :
274 47 : double endingX = endingXForOpenEndedSlurTie(outgoingPartialSlur ? measure : system->lastMeasure(), item->startCR()->vStaffIdx());
275 47 : p2 = PointF(endingX, y);
276 :
277 : // adjust for ties at the end of the system
278 47 : ChordRest* cr = outgoingPartialSlur ? measure->lastChordRest(item->track()) : system->lastChordRest(item->track());
279 47 : const bool tieSameStaff = cr && cr->vStaffIdx() == item->staffIdx();
280 :
281 47 : if (cr && cr->isChord() && cr->tick() >= stick && cr->tick() <= etick && tieSameStaff) {
282 40 : Chord* c = toChord(cr);
283 40 : Tie* tie = nullptr;
284 40 : PointF endPoint;
285 40 : Tie* tieFor = c->upNote()->tieFor();
286 26 : if (tieFor && (tieFor->isPartialTie() || !tieFor->isInside()) && tieFor->up() == item->up()
287 66 : && !tieSegmentShouldBeSkipped(tieFor)) {
288 : // there is a tie that starts on this chordrest
289 26 : if (!tieFor->segmentsEmpty()) { //Checks is spanner segment exists
290 26 : tie = tieFor;
291 26 : endPoint = tie->segmentAt(0)->ups(Grip::END).pos();
292 : }
293 :
294 26 : if (outgoingPartialSlur && tie->type() == ElementType::TIE && tie->nsegments() == 1) {
295 : // For partial slurs ending midway through a tie, get top of the tie shape at the slur's end X
296 0 : const TieSegment* tieSeg = tie->frontSegment();
297 0 : const Shape tieShape = tieSeg->shape().translate(tieSeg->pos());
298 0 : if (item->up() && tie->up()) {
299 0 : endPoint.ry() = p2.y() + tieShape.topDistance(p2);
300 0 : } else if (!item->up() && !tie->up()) {
301 0 : endPoint.ry() = p2.y() - tieShape.bottomDistance(p2);
302 : }
303 0 : }
304 : }
305 40 : if (tie) {
306 26 : if (item->up() && tie->up()) {
307 0 : if (endPoint.y() - p2.y() < tieClearance) {
308 0 : p2.ry() = endPoint.y() - tieClearance;
309 : }
310 26 : } else if (!item->up() && !tie->up()) {
311 26 : if (p2.y() - endPoint.y() < tieClearance) {
312 26 : p2.ry() = endPoint.y() + tieClearance;
313 : }
314 : }
315 : }
316 : }
317 : }
318 929 : if (constrainLeftAnchor) {
319 21 : p1.ry() = p2.y() + (0.25 * item->spatium() * (item->up() ? -1 : 1));
320 : }
321 :
322 929 : if (item->isTappingHalfSlur()) {
323 32 : p1 = p2 - PointF(2.5 * item->spatium(), 0.0);
324 : }
325 :
326 911 : bool isHangingSlur = sst == SpannerSegmentType::BEGIN || sst == SpannerSegmentType::END || incomingPartialSlur
327 1840 : || outgoingPartialSlur;
328 929 : if (isHangingSlur && ctx.conf().styleB(Sid::angleHangingSlursAwayFromStaff)) {
329 0 : adjustSlurFloatingEndPointAngles(slurSegment, p1, p2, incomingPartialSlur, outgoingPartialSlur);
330 : }
331 :
332 929 : layoutSegment(slurSegment, p1, p2);
333 :
334 929 : return slurSegment;
335 : }
336 :
337 0 : void SlurTieLayout::adjustSlurFloatingEndPointAngles(SlurSegment* slurSeg, PointF& p1, PointF& p2, bool incomingPartial,
338 : bool outgoingPartial)
339 : {
340 0 : bool startIsHanging = slurSeg->spannerSegmentType() == SpannerSegmentType::END || incomingPartial;
341 0 : bool endIsHanging = slurSeg->spannerSegmentType() == SpannerSegmentType::BEGIN || outgoingPartial;
342 :
343 0 : IF_ASSERT_FAILED(startIsHanging != endIsHanging) {
344 0 : return;
345 : }
346 :
347 0 : bool up = slurSeg->slur()->up();
348 0 : const double heightDiff = 1.0 * slurSeg->spatium();
349 :
350 0 : if (startIsHanging) {
351 0 : double yCur = p1.y();
352 0 : p1.setY(up ? std::min(yCur, p2.y() - heightDiff) : std::max(yCur, p2.y() + heightDiff));
353 0 : } else if (endIsHanging) {
354 0 : double yCur = p2.y();
355 0 : p2.setY(up ? std::min(yCur, p1.y() - heightDiff) : std::max(yCur, p1.y() + heightDiff));
356 : }
357 : }
358 :
359 615 : double SlurTieLayout::endingXForOpenEndedSlurTie(const Measure* measure, staff_idx_t staffIdx)
360 : {
361 615 : double endingX = measure->endingXForOpenEndedLines();
362 :
363 615 : const SegmentType segmentTypesToAvoid = SegmentType::TimeSigTypes | SegmentType::KeySigTypes | SegmentType::ClefTypes;
364 :
365 : // Find the first segment at the end of the measure which meets the criteria for the SlurTie to stop before it:
366 615 : const Segment* endSegment = measure->findSegmentR(segmentTypesToAvoid, measure->ticks());
367 615 : const EngravingItem* endItem = nullptr;
368 617 : while (endSegment) {
369 2 : if (endSegment->isType(segmentTypesToAvoid) && endSegment->enabled()) {
370 0 : endItem = endSegment->element(staff2track(staffIdx));
371 0 : if (endItem && endItem->visible() && endItem->addToSkyline()) {
372 0 : break;
373 : }
374 : }
375 2 : endSegment = endSegment->next();
376 : }
377 :
378 615 : if (!endSegment || !endItem) {
379 615 : return endingX;
380 : }
381 :
382 0 : const double margin = endItem->style().styleAbsolute(Sid::lineEndToBarlineDistance);
383 0 : const double itemLeftX = measure->x() + endSegment->x() + endItem->ldata()->shape().translated(endItem->pos()).left();
384 :
385 0 : return std::min(endingX, itemLeftX - margin);
386 : }
387 :
388 : //---------------------------------------------------------
389 : // slurPos
390 : // calculate position of start- and endpoint of slur
391 : // relative to System() position
392 : //---------------------------------------------------------
393 :
394 929 : void SlurTieLayout::slurPos(Slur* item, SlurTiePos* sp, LayoutContext& ctx)
395 : {
396 929 : item->stemFloated().reset();
397 929 : double _spatium = (item->staffType() ? item->staffType()->lineDistance().val() : 1.0) * item->spatium();
398 929 : const double stemSideInset = 0.5;
399 929 : const double stemOffsetX = 0.35;
400 929 : const double beamClearance = 0.35;
401 929 : const double beamAnchorInset = 0.15;
402 929 : const double straightStemXOffset = 0.5; // how far down a straight stem a slur attaches (percent)
403 929 : const double minOffset = 0.2;
404 : // hack alert!! -- fakeCutout
405 : // The fakeCutout const describes the slope of a line from the top of the stem to the full width of the hook.
406 : // this is necessary because hooks don't have SMuFL cutouts
407 : // Gonville and MuseJazz have really weirdly-shaped hooks compared to Leland and Bravura and Emmentaler,
408 : // so we need to adjust the slope of our hook-avoidance line. this will be unnecessary when hooks have
409 : // SMuFL anchors
410 929 : const std::string fontName = muse::strings::toLower(ctx.engravingFont()->name());
411 929 : bool bulkyHook = fontName == "gonville" || fontName == "musejazz";
412 929 : const double fakeCutoutSlope = bulkyHook ? 1.5 : 1.0;
413 :
414 929 : if (item->endCR() == 0) {
415 0 : sp->p1 = item->startCR()->pagePos();
416 0 : sp->p1.rx() += item->startCR()->width();
417 0 : sp->p2 = sp->p1;
418 0 : sp->p2.rx() += 5 * _spatium;
419 0 : sp->system1 = item->startCR()->measure()->system();
420 0 : sp->system2 = sp->system1;
421 0 : return;
422 : }
423 :
424 929 : bool useTablature = item->staff() && item->staff()->isTabStaff(item->endCR()->tick());
425 929 : bool staffHasStems = true; // assume staff uses stems
426 929 : const StaffType* stt = 0;
427 929 : if (useTablature) {
428 31 : stt = item->staff()->staffType(item->tick());
429 31 : staffHasStems = stt->stemThrough(); // if tab with stems beside, stems do not count for slur pos
430 : }
431 :
432 : // start and end cr, chord, and note
433 929 : ChordRest* scr = item->startCR();
434 929 : ChordRest* ecr = item->endCR();
435 929 : Chord* sc = 0;
436 929 : Note* note1 = 0;
437 929 : if (scr->isChord()) {
438 929 : sc = toChord(scr);
439 929 : note1 = item->up() ? sc->upNote() : sc->downNote();
440 : }
441 929 : Chord* ec = 0;
442 929 : Note* note2 = 0;
443 929 : if (ecr->isChord()) {
444 916 : ec = toChord(ecr);
445 916 : note2 = item->up() ? ec->upNote() : ec->downNote();
446 : }
447 :
448 929 : sp->system1 = scr->measure()->system();
449 929 : sp->system2 = ecr->measure()->system();
450 :
451 929 : if (sp->system1 == 0) {
452 0 : LOGD("no system1");
453 0 : return;
454 : }
455 :
456 929 : sp->p1 = scr->pos() + scr->segment()->pos() + scr->measure()->pos();
457 929 : sp->p2 = ecr->pos() + ecr->segment()->pos() + ecr->measure()->pos();
458 929 : if (scr->isGrace()) {
459 49 : sp->p1 += scr->layoutParent()->pos();
460 : }
461 929 : if (ecr->isGrace()) {
462 38 : sp->p2 += ecr->layoutParent()->pos();
463 : }
464 :
465 : // adjust for cross-staff
466 929 : if (scr->vStaffIdx() != item->vStaffIdx() && sp->system1) {
467 9 : double diff = sp->system1->staff(scr->vStaffIdx())->y() - sp->system1->staff(item->vStaffIdx())->y();
468 9 : sp->p1.ry() += diff;
469 : }
470 929 : if (ecr->vStaffIdx() != item->vStaffIdx() && sp->system2) {
471 21 : double diff = sp->system2->staff(ecr->vStaffIdx())->y() - sp->system2->staff(item->vStaffIdx())->y();
472 21 : sp->p2.ry() += diff;
473 : }
474 :
475 : // account for centering or other adjustments (other than mirroring)
476 929 : if (note1 && !note1->ldata()->mirror.value()) {
477 929 : sp->p1.rx() += note1->x();
478 : }
479 929 : if (note2 && !note2->ldata()->mirror.value()) {
480 916 : sp->p2.rx() += note2->x();
481 : }
482 :
483 929 : PointF po = PointF();
484 :
485 929 : Stem* stem1 = sc && staffHasStems ? sc->stem() : 0;
486 929 : Stem* stem2 = ec && staffHasStems ? ec->stem() : 0;
487 :
488 : enum class SlurAnchor : char {
489 : NONE, STEM
490 : };
491 929 : SlurAnchor sa1 = SlurAnchor::NONE;
492 929 : SlurAnchor sa2 = SlurAnchor::NONE;
493 929 : if (staffHasStems) {
494 899 : if (sc && sc->hook() && sc->up() == item->up()) {
495 6 : sa1 = SlurAnchor::STEM;
496 : }
497 899 : if (scr->up() == ecr->up() && scr->up() == item->up()) {
498 118 : if (stem1 && !stemSideStartForBeam(item)) {
499 91 : sa1 = SlurAnchor::STEM;
500 : }
501 118 : if (stem2 && !stemSideEndForBeam(item)) {
502 91 : sa2 = SlurAnchor::STEM;
503 : }
504 781 : } else if (ecr->segment()->system() != scr->segment()->system()) {
505 : // in the case of continued slurs, we anchor to stem when necessary
506 34 : if (scr->up() == item->up() && stem1 && !scr->beam()) {
507 12 : sa1 = SlurAnchor::STEM;
508 : }
509 34 : if (ecr->up() == item->up() && stem2 && !ecr->beam()) {
510 0 : sa2 = SlurAnchor::STEM;
511 : }
512 : }
513 : }
514 :
515 929 : double __up = item->up() ? -1.0 : 1.0;
516 929 : double hw1 = note1 ? note1->tabHeadWidth(stt) : scr->width() * scr->mag(); // if stt == 0, tabHeadWidth()
517 929 : double hw2 = note2 ? note2->tabHeadWidth(stt) : ecr->width() * ecr->mag(); // defaults to headWidth()
518 929 : PointF pt;
519 929 : switch (sa1) {
520 108 : case SlurAnchor::STEM: //sc can't be null
521 : {
522 : // place slur starting point at stem end point
523 108 : pt = StemLayout::stemPos(sc) - sc->pagePos() + sc->stem()->ldata()->line.p2();
524 108 : if (useTablature) { // in tabs, stems are centred on note:
525 0 : pt.rx() = hw1 * 0.5 + (note1 ? note1->bboxXShift() : 0.0); // skip half notehead to touch stem, anatoly-os: incorrect. half notehead width is not always the stem position
526 : }
527 : // clear the stem (x)
528 : // allow slight overlap (y)
529 : // don't allow overlap with hook if not disabling the autoplace checks against start/end segments in SlurSegment::layoutSegment()
530 108 : double yadj = -stemSideInset* sc->intrinsicMag();
531 108 : yadj *= _spatium * __up;
532 108 : double offset = std::max(stemOffsetX * sc->intrinsicMag(), minOffset);
533 108 : pt += PointF(offset * _spatium, yadj);
534 : // account for articulations
535 108 : fixArticulations(item, pt, sc, __up, true);
536 : // adjust for hook
537 108 : double fakeCutout = 0.0;
538 108 : if (!ctx.conf().styleB(Sid::useStraightNoteFlags)) {
539 108 : Hook* hook = sc->hook();
540 : // regular flags
541 :
542 108 : if (hook && hook->ldata()->bbox().translated(hook->pos()).contains(pt)) {
543 : // TODO: in the utopian far future where all hooks have SMuFL cutouts, this fakeCutout business will no
544 : // longer be used. for the time being fakeCutout describes a point on the line y=mx+b, out from the top of the stem
545 : // where y = yadj, m = fakeCutoutSlope, and x = y/m + fakeCutout
546 6 : fakeCutout = std::min(0.0, std::abs(yadj) - (hook->width() / fakeCutoutSlope));
547 6 : pt.rx() = StemLayout::stemPosX(sc) - fakeCutout;
548 : }
549 : } else {
550 0 : Hook* hook = sc->hook();
551 : // straight flags
552 0 : if (hook && hook->ldata()->bbox().translated(hook->pos()).contains(pt)) {
553 0 : double hookWidth = hook->width() * hook->mag();
554 0 : pt.rx() = (hookWidth * straightStemXOffset) + (hook->pos().x() + sc->x());
555 0 : if (item->up()) {
556 0 : pt.ry() = sc->downNote()->pos().y() - stem1->height() - (beamClearance * _spatium * .7);
557 : } else {
558 0 : pt.ry() = sc->upNote()->pos().y() + stem1->height() + (beamClearance * _spatium * .7);
559 : }
560 : }
561 : }
562 108 : sp->p1 += pt;
563 : }
564 108 : break;
565 821 : case SlurAnchor::NONE:
566 821 : break;
567 : }
568 929 : switch (sa2) {
569 91 : case SlurAnchor::STEM: //ec can't be null
570 : {
571 91 : pt = StemLayout::stemPos(ec) - ec->pagePos() + ec->stem()->ldata()->line.p2();
572 91 : if (useTablature) {
573 0 : pt.rx() = hw2 * 0.5;
574 : }
575 : // don't allow overlap with beam
576 : double yadj;
577 91 : if (ec->beam() && ec->beam()->elements().front() != ec) {
578 0 : yadj = 0.75;
579 91 : } else if (ec->tremoloTwoChord() && ec->tremoloTwoChord()->chord2() == ec) {
580 0 : yadj = 0.75;
581 : } else {
582 91 : yadj = -stemSideInset;
583 : }
584 91 : yadj *= _spatium * __up;
585 91 : double offset = std::max(stemOffsetX * ec->intrinsicMag(), minOffset);
586 91 : pt += PointF(-offset * _spatium, yadj);
587 : // account for articulations
588 91 : fixArticulations(item, pt, ec, __up, true);
589 91 : sp->p2 += pt;
590 : }
591 91 : break;
592 838 : case SlurAnchor::NONE:
593 838 : break;
594 : }
595 :
596 : //
597 : // default position:
598 : // horizontal: middle of notehead
599 : // vertical: _spatium * .4 above/below notehead
600 : //
601 : //------p1
602 : // Compute x0, y0 and stemPos
603 929 : if (sa1 == SlurAnchor::NONE || sa2 == SlurAnchor::NONE) { // need stemPos if sa2 == SlurAnchor::NONE
604 840 : bool stemPos = false; // p1 starts at chord stem side
605 :
606 : // default positions
607 840 : po.rx() = hw1 * .5 + (note1 ? note1->bboxXShift() : 0.0);
608 840 : if (note1) {
609 840 : po.ry() = note1->pos().y();
610 0 : } else if (item->up()) {
611 0 : po.ry() = scr->ldata()->bbox().top();
612 : } else {
613 0 : po.ry() = scr->ldata()->bbox().top() + scr->height();
614 : }
615 840 : double offset = useTablature ? 0.75 : 0.9;
616 840 : po.ry() += scr->intrinsicMag() * _spatium * offset * __up;
617 :
618 : // adjustments for stem and/or beam
619 840 : TremoloTwoChord* trem = sc ? sc->tremoloTwoChord() : nullptr;
620 840 : if (stem1 || trem) { //sc not null
621 766 : Beam* beam1 = sc->beam();
622 766 : if (stem1 && beam1 && (beam1->elements().back() != sc) && (sc->up() == item->up())) {
623 33 : TLayout::layoutBeam(beam1, ctx);
624 : // start chord is beamed but not the last chord of beam group
625 : // and slur direction is same as start chord (stem side)
626 :
627 : // in these cases, layout start of slur to stem
628 33 : double beamWidthSp = ctx.conf().styleS(Sid::beamWidth).val() * beam1->magS();
629 33 : double offset2 = std::max(beamClearance * sc->intrinsicMag(), minOffset) * _spatium;
630 33 : double sh = stem1->length() + (beamWidthSp / 2) + offset2;
631 33 : if (item->up()) {
632 26 : po.ry() = StemLayout::stemPos(sc).y() - sc->pagePos().y() - sh;
633 : } else {
634 7 : po.ry() = StemLayout::stemPos(sc).y() - sc->pagePos().y() + sh;
635 : }
636 33 : po.rx() = StemLayout::stemPosX(sc) + (beamAnchorInset * _spatium * sc->intrinsicMag()) + (stem1->lineWidthMag() / 2 * __up);
637 :
638 : // account for articulations
639 33 : fixArticulations(item, po, sc, __up, true);
640 :
641 : // force end of slur to layout to stem as well,
642 : // if start and end chords have same stem direction
643 33 : stemPos = true;
644 733 : } else if (trem && trem->chord2() != sc && sc->up() == item->up()) {
645 0 : TremoloLayout::layout(trem, ctx);
646 0 : Note* note = item->up() ? sc->upNote() : sc->downNote();
647 0 : double stemHeight = stem1 ? stem1->length() : defaultStemLengthStart(trem);
648 0 : double offset2 = std::max(beamClearance * sc->intrinsicMag(), minOffset) * _spatium;
649 0 : double sh = stemHeight + offset2;
650 :
651 0 : if (item->up()) {
652 0 : po.ry() = StemLayout::stemPos(sc).y() - sc->pagePos().y() - sh;
653 : } else {
654 0 : po.ry() = StemLayout::stemPos(sc).y() - sc->pagePos().y() + sh;
655 : }
656 0 : if (!stem1) {
657 0 : po.rx() = note->noteheadCenterX();
658 : } else {
659 0 : po.rx() = StemLayout::stemPosX(sc) + (beamAnchorInset * _spatium * sc->intrinsicMag())
660 0 : + (stem1->lineWidthMag() / 2. * __up);
661 : }
662 0 : fixArticulations(item, po, sc, __up, true);
663 :
664 0 : stemPos = true;
665 : } else {
666 : // start chord is not beamed or is last chord of beam group
667 : // or slur direction is opposite that of start chord
668 :
669 : // at this point slur is in default position relative to note on slur side
670 : // but we may need to make further adjustments
671 :
672 : // if stem and slur are both up
673 : // we need to clear stem horizontally
674 733 : double stemOffsetMag = stemOffsetX * sc->intrinsicMag();
675 733 : if (sc->up() && item->up()) {
676 : // stems in tab staves come from the middle of the head, which means it's much easier
677 : // to just subtract an offset from the notehead center (which po already is)
678 57 : if (useTablature) {
679 0 : po.rx() += stemOffsetMag * _spatium;
680 : } else {
681 57 : po.rx() = hw1 + _spatium * stemOffsetMag;
682 : }
683 : }
684 :
685 : //
686 : // handle case: stem up - stem down
687 : // stem down - stem up
688 : //
689 733 : if ((sc->up() != ecr->up()) && (sc->up() == item->up())) {
690 59 : item->stemFloated().left = true;
691 : // start and end chord have opposite direction
692 : // and slur direction is same as start chord
693 : // (so slur starts on stem side)
694 :
695 : // float the start point along the stem to follow direction of movement
696 : // see for example Gould p. 111
697 :
698 : // get position of note on slur side for start & end chords
699 59 : Note* n1 = sc->up() ? sc->upNote() : sc->downNote();
700 59 : Note* n2 = 0;
701 59 : if (ec) {
702 59 : n2 = ec->up() ? ec->upNote() : ec->downNote();
703 : }
704 :
705 : // differential in note positions
706 59 : double yd = (n2 ? n2->pos().y() : ecr->pos().y()) - n1->pos().y();
707 59 : yd *= .5;
708 :
709 : // float along stem according to differential
710 59 : double sh = stem1 ? stem1->height() : 0.0;
711 59 : if (item->up() && yd < 0.0) {
712 45 : po.ry() = std::max(po.y() + yd, sc->downNote()->pos().y() - sh - _spatium);
713 14 : } else if (!item->up() && yd > 0.0) {
714 0 : po.ry() = std::min(po.y() + yd, sc->upNote()->pos().y() + sh + _spatium);
715 : }
716 :
717 : // account for articulations
718 59 : fixArticulations(item, po, sc, __up, true);
719 :
720 : // we may wish to force end to align to stem as well,
721 : // if it is in same direction
722 : // (but it won't be, so this assignment should have no effect)
723 59 : stemPos = true;
724 : } else {
725 : // avoid articulations
726 674 : fixArticulations(item, po, sc, __up, sc->up() == item->up());
727 : }
728 : }
729 840 : } else if (sc) {
730 : // avoid articulations
731 74 : fixArticulations(item, po, sc, __up, sc->up() == item->up());
732 : }
733 :
734 : // TODO: offset start position if there is another slur ending on this cr
735 :
736 840 : if (sa1 == SlurAnchor::NONE) {
737 821 : sp->p1 += po;
738 : }
739 :
740 : //------p2
741 840 : if (sa2 == SlurAnchor::NONE) {
742 : // default positions
743 838 : po.rx() = hw2 * .5 + (note2 ? note2->bboxXShift() : 0.0);
744 838 : if (note2) {
745 825 : po.ry() = note2->pos().y();
746 13 : } else if (item->up()) {
747 0 : po.ry() = item->endCR()->ldata()->bbox().top();
748 : } else {
749 13 : po.ry() = item->endCR()->ldata()->bbox().top() + item->endCR()->height();
750 : }
751 838 : double offset2 = useTablature ? 0.75 : 0.9;
752 838 : po.ry() += ecr->intrinsicMag() * _spatium * offset2 * __up;
753 :
754 : // adjustments for stem and/or beam
755 838 : TremoloTwoChord* trem2 = ec ? ec->tremoloTwoChord() : nullptr;
756 838 : if (stem2 || trem2) { //ec can't be null
757 738 : Beam* beam2 = ec->beam();
758 88 : if ((stemPos && (scr->up() == ec->up()))
759 715 : || (beam2
760 206 : && (!beam2->elements().empty())
761 206 : && (beam2->elements().front() != ec)
762 125 : && (ec->up() == item->up())
763 4 : && sc && (sc->noteType() == NoteType::NORMAL)
764 : )
765 1476 : || (trem2 && ec->up() == item->up())
766 : ) {
767 27 : if (beam2) {
768 27 : TLayout::layoutBeam(beam2, ctx);
769 : }
770 27 : if (trem2) {
771 0 : TremoloLayout::layout(trem2, ctx);
772 : }
773 : // slur start was laid out to stem and start and end have same direction
774 : // OR
775 : // end chord is beamed but not the first chord of beam group
776 : // and slur direction is same as end chord (stem side)
777 : // and start chordrest is not a grace chord
778 :
779 : // in these cases, layout end of slur to stem
780 27 : double beamWidthSp = beam2 ? ctx.conf().styleS(Sid::beamWidth).val() : 0;
781 27 : Note* note = item->up() ? ec->upNote() : ec->downNote();
782 27 : double stemHeight = stem2 ? stem2->length() + (beamWidthSp / 2) : defaultStemLengthEnd(trem2);
783 27 : double offset3 = std::max(beamClearance * ec->intrinsicMag(), minOffset) * _spatium;
784 27 : double sh = stemHeight + offset3;
785 :
786 27 : if (item->up()) {
787 20 : po.ry() = StemLayout::stemPos(ec).y() - ec->pagePos().y() - sh;
788 : } else {
789 7 : po.ry() = StemLayout::stemPos(ec).y() - ec->pagePos().y() + sh;
790 : }
791 27 : if (!stem2) {
792 : // tremolo whole notes
793 0 : po.setX(note->noteheadCenterX());
794 : } else {
795 27 : po.setX(StemLayout::stemPosX(ec) + (stem2->lineWidthMag() / 2 * __up)
796 27 : - (beamAnchorInset * _spatium * ec->intrinsicMag()));
797 : }
798 :
799 : // account for articulations
800 27 : fixArticulations(item, po, ec, __up, true);
801 : } else {
802 : // slur was not aligned to stem or start and end have different direction
803 : // AND
804 : // end chord is not beamed or is first chord of beam group
805 : // or slur direction is opposite that of end chord
806 :
807 : // if stem and slur are both down,
808 : // we need to clear stem horizontally
809 711 : double stemOffsetMag = stemOffsetX * ec->intrinsicMag();
810 711 : if (!ec->up() && !item->up()) {
811 : // stems in tab staves come from the middle of the head, which means it's much easier
812 : // to just subtract an offset from the notehead center (which po already is)
813 17 : if (useTablature) {
814 0 : po.rx() -= stemOffsetMag * _spatium;
815 : } else {
816 17 : po.rx() = -_spatium * stemOffsetMag + note2->x();
817 : }
818 694 : } else if (useTablature && item->up() && ec->up()) {
819 : // same as above
820 0 : po.rx() -= _spatium * stemOffsetMag;
821 : }
822 :
823 : //
824 : // handle case: stem up - stem down
825 : // stem down - stem up
826 : //
827 711 : if ((scr->up() != ec->up()) && (ec->up() == item->up())) {
828 46 : item->stemFloated().right = true;
829 : // start and end chord have opposite direction
830 : // and slur direction is same as end chord
831 : // (so slur end on stem side)
832 :
833 : // float the end point along the stem to follow direction of movement
834 : // see for example Gould p. 111
835 :
836 46 : Note* n1 = 0;
837 46 : if (sc) {
838 46 : n1 = sc->up() ? sc->upNote() : sc->downNote();
839 : }
840 46 : Note* n2 = ec->up() ? ec->upNote() : ec->downNote();
841 :
842 46 : double yd = n2->pos().y() - (n1 ? n1->pos().y() : item->startCR()->pos().y());
843 46 : yd *= .5;
844 :
845 46 : double mh = stem2 ? stem2->height() : 0.0;
846 46 : if (item->up() && yd > 0.0) {
847 26 : po.ry() = std::max(po.y() - yd, ec->downNote()->pos().y() - mh - _spatium);
848 20 : } else if (!item->up() && yd < 0.0) {
849 15 : po.ry() = std::min(po.y() - yd, ec->upNote()->pos().y() + mh + _spatium);
850 : }
851 :
852 : // account for articulations
853 46 : fixArticulations(item, po, ec, __up, true);
854 : } else {
855 : // avoid articulations
856 665 : fixArticulations(item, po, ec, __up, ec->up() == item->up());
857 : }
858 : }
859 838 : } else if (ec) {
860 : // avoid articulations
861 87 : fixArticulations(item, po, ec, __up, ec->up() == item->up());
862 : }
863 : // TODO: offset start position if there is another slur ending on this cr
864 838 : sp->p2 += po;
865 : }
866 : }
867 :
868 929 : if (item->staffType()->isTabStaff()) {
869 31 : SlurTieLayout::avoidPreBendsOnTab(sc, ec, sp);
870 : }
871 :
872 929 : if (item->isTappingHalfSlur()) {
873 32 : adjustForTappingHalfSlurs(toTappingHalfSlur(item), sp, note2);
874 : }
875 929 : }
876 :
877 32 : void SlurTieLayout::adjustForTappingHalfSlurs(TappingHalfSlur* item, SlurTiePos* sp, Note* endNote)
878 : {
879 32 : if (item->staffType()->isTabStaff()) {
880 16 : return;
881 : }
882 :
883 16 : int staffLines = endNote->staff()->lines(endNote->tick());
884 16 : bool noteIsInsideStaff = endNote->line() > 1 && endNote->line() < 2 * (staffLines - 1) - 1;
885 16 : bool noteIsOnSpace = endNote->line() % 2 != 0;
886 16 : if (noteIsInsideStaff && noteIsOnSpace) {
887 0 : sp->p2.ry() += (item->up() ? 0.2 : -0.2) * item->spatium();
888 : }
889 : }
890 :
891 1864 : void SlurTieLayout::fixArticulations(Slur* item, PointF& pt, Chord* c, double up, bool stemSide)
892 : {
893 : //
894 : // handle special case of tenuto and staccato
895 : // yo = current offset of slur from chord position
896 : // return unchanged position, or position of outmost "close" articulation
897 : //
898 1864 : double slurTipToArticVertDist = c->spatium() * 0.5 * up;
899 1864 : double slurTipInwardAdjust = 0.1 * item->spatium();
900 1864 : bool start = item->startCR() && item->startCR() == c;
901 1864 : bool end = item->endCR() && item->endCR() == c;
902 1964 : for (Articulation* a : c->articulations()) {
903 100 : if (!a->layoutCloseToNote() || !a->addToSkyline()) {
904 70 : continue;
905 : }
906 : // skip if articulation on stem side but slur is not or vice versa
907 30 : if ((a->up() == c->up()) != stemSide) {
908 0 : continue;
909 : }
910 : // Correct x-position inwards
911 30 : Note* note = c->up() ? c->downNote() : c->upNote();
912 30 : pt.rx() = a->x() + 0.5 * a->width() - note->x();
913 30 : if (start) {
914 13 : pt.rx() += slurTipInwardAdjust;
915 17 : } else if (end) {
916 17 : pt.rx() -= slurTipInwardAdjust;
917 : }
918 : // Adjust y-position
919 30 : if (a->up()) {
920 12 : pt.ry() = std::min(pt.y(), a->y() + a->height() / 2 * up + slurTipToArticVertDist);
921 : } else {
922 18 : pt.ry() = std::max(pt.y(), a->y() + a->height() / 2 * up + slurTipToArticVertDist);
923 : }
924 : }
925 1864 : }
926 :
927 1900 : void SlurTieLayout::adjustEndPoints(SlurSegment* slurSeg)
928 : {
929 1900 : double spatium = slurSeg->spatium();
930 1900 : double lw = (slurSeg->staffType() ? slurSeg->staffType()->lineDistance().val() : 1.0) * spatium;
931 1900 : const double staffLineMargin = 0.175 + (0.5 * slurSeg->style().styleS(Sid::staffLineWidth).val() * (spatium / lw));
932 1900 : PointF p1 = slurSeg->ups(Grip::START).p;
933 1900 : PointF p2 = slurSeg->ups(Grip::END).p;
934 :
935 1900 : double y1sp = p1.y() / lw;
936 1900 : double y2sp = p2.y() / lw;
937 :
938 : // point 1
939 1900 : const Staff* staff = slurSeg->staff();
940 1900 : int lines = staff ? staff->lines(slurSeg->tick()) : 0;
941 1850 : auto adjustPoint = [staffLineMargin](bool up, double ysp) {
942 1850 : double y1offset = ysp - floor(ysp);
943 1850 : double adjust = 0;
944 1850 : if (up) {
945 964 : if (y1offset < staffLineMargin) {
946 : // endpoint too close to the line above
947 174 : adjust = -(y1offset + staffLineMargin);
948 790 : } else if (y1offset > 1 - staffLineMargin) {
949 : // endpoint too close to the line below
950 81 : adjust = -(y1offset - (1 - staffLineMargin));
951 : }
952 : } else {
953 886 : if (y1offset < staffLineMargin) {
954 : // endpoint too close to the line above
955 239 : adjust = staffLineMargin - y1offset;
956 : }
957 886 : if (y1offset > 1 - staffLineMargin) {
958 : // endpoint too close to the line below
959 236 : adjust = (1 - y1offset) + staffLineMargin;
960 : }
961 : }
962 1850 : return adjust;
963 1900 : };
964 1900 : if (y1sp > -staffLineMargin && y1sp < (lines - 1) + staffLineMargin) {
965 974 : slurSeg->ups(Grip::START).p.ry() += adjustPoint(slurSeg->slur()->up(), y1sp) * lw;
966 : }
967 1900 : if (y2sp > -staffLineMargin && y2sp < (lines - 1) + staffLineMargin) {
968 876 : slurSeg->ups(Grip::END).p.ry() += adjustPoint(slurSeg->slur()->up(), y2sp) * lw;
969 : }
970 1900 : }
971 :
972 915 : void SlurTieLayout::avoidCollisions(SlurSegment* slurSeg, PointF& pp1, PointF& p2, PointF& p3, PointF& p4,
973 : Transform& toSystemCoordinates, double& slurAngle)
974 : {
975 924 : TRACEFUNC;
976 915 : Slur* slur = slurSeg->slur();
977 915 : double spatium = slurSeg->spatium();
978 915 : double slurLength = std::abs(p2.x() / spatium);
979 915 : bool slurUp = slur->up();
980 915 : int upSign = slurUp ? -1 : 1;
981 :
982 915 : ChordRest* startCR = slur->startCR();
983 915 : ChordRest* endCR = slur->endCR();
984 :
985 915 : if (!startCR || !endCR) {
986 0 : return;
987 : }
988 :
989 915 : Shape segShapes = getSegmentShapes(slurSeg, startCR, endCR); // Shape of the music under this slur
990 915 : addMinClearanceToShapes(segShapes, spatium, slurUp, startCR, endCR);
991 :
992 915 : if (segShapes.empty()) {
993 30 : return;
994 : }
995 :
996 885 : const double arcClearance = -upSign* computeArcClearance(spatium, slurLength, slurAngle); // Collision clearance at the center of the slur
997 :
998 : // balance: determines how much endpoint adjustment VS shape adjustment we will do.
999 : // 0 = end point is fixed, only the shape can be adjusted,
1000 : // 1 = shape is fixed, only end the point can be adjusted.
1001 885 : double leftBalance = 0.0;
1002 885 : double rightBalance = 0.0;
1003 885 : computeAdjustmentBalance(slurSeg, startCR, endCR, leftBalance, rightBalance);
1004 :
1005 : static constexpr unsigned MAX_ITER = 30; // Max iterations allowed
1006 :
1007 885 : double step = computeAdjustmentStep(upSign, spatium, slurLength);
1008 :
1009 : // Divide slur in several rectangles to localize collisions
1010 885 : const unsigned npoints = 20;
1011 885 : std::vector<RectF> slurRects;
1012 885 : slurRects.reserve(npoints);
1013 :
1014 : // Define separate collision areas (left-mid-center)
1015 : struct SlurCollision
1016 : {
1017 : bool left = false;
1018 : bool mid = false;
1019 : bool right = false;
1020 :
1021 2087 : void reset()
1022 : {
1023 2087 : left = false;
1024 2087 : mid = false;
1025 2087 : right = false;
1026 2087 : }
1027 : };
1028 885 : SlurCollision collision;
1029 :
1030 : // CHECK FOR COLLISIONS
1031 885 : unsigned iter = 0;
1032 : do {
1033 2087 : collision.reset();
1034 : // Update tranform because pp1 may change
1035 2087 : toSystemCoordinates.reset();
1036 2087 : toSystemCoordinates.translate(pp1.x(), pp1.y());
1037 2087 : toSystemCoordinates.rotateRadians(slurAngle);
1038 : // Create rectangles
1039 2087 : slurRects.clear();
1040 2087 : CubicBezier clearanceBezier(PointF(0, 0), p3 + PointF(0.0, arcClearance), p4 + PointF(0.0, arcClearance), p2);
1041 41740 : for (unsigned i = 0; i < npoints - 1; i++) {
1042 39653 : PointF clearancePoint1 = clearanceBezier.pointAtPercent(double(i) / double(npoints));
1043 39653 : PointF clearancePoint2 = clearanceBezier.pointAtPercent(double(i + 1) / double(npoints));
1044 39653 : clearancePoint1 = toSystemCoordinates.map(clearancePoint1);
1045 39653 : clearancePoint2 = toSystemCoordinates.map(clearancePoint2);
1046 39653 : slurRects.push_back(RectF(clearancePoint1, clearancePoint2));
1047 : }
1048 : // Check collisions
1049 41740 : for (unsigned i=0; i < slurRects.size(); i++) {
1050 39653 : bool leftSection = i < slurRects.size() / 3;
1051 39653 : bool midSection = i >= slurRects.size() / 3 && i < 2 * slurRects.size() / 3;
1052 39653 : bool rightSection = i >= 2 * slurRects.size() / 3;
1053 39653 : if ((leftSection && collision.left)
1054 38168 : || (midSection && collision.mid)
1055 35998 : || (rightSection && collision.right)) { // If a collision is already found in this section, no need to check again
1056 7376 : continue;
1057 : }
1058 46894 : bool intersection = slur->up() ? !Shape(slurRects[i]).clearsVertically(segShapes)
1059 32277 : : !segShapes.clearsVertically(slurRects[i]);
1060 32277 : if (intersection) {
1061 1961 : if (leftSection) {
1062 556 : collision.left = true;
1063 : }
1064 1961 : if (midSection) {
1065 594 : collision.mid = true;
1066 : }
1067 1961 : if (rightSection) {
1068 811 : collision.right = true;
1069 : }
1070 : }
1071 : }
1072 :
1073 : // In the even iterations, adjust the shape
1074 2087 : if (iter % 2 == 0) {
1075 : static constexpr double SHAPE_PREFER_CENTER_FACTOR = 2.0;
1076 1454 : if (collision.left) {
1077 290 : double shapeLeftStep = (1 - leftBalance) * step / SHAPE_PREFER_CENTER_FACTOR;
1078 : // Move left Bezier point up(/down) and outwards
1079 290 : p3 += PointF(-abs(shapeLeftStep), shapeLeftStep);
1080 : // and a bit also the right point to compensate asymmetry
1081 290 : p4 += PointF(std::abs(shapeLeftStep), shapeLeftStep) / 2.0;
1082 : }
1083 1454 : if (collision.mid) { // Move both Bezier points up(/down)
1084 320 : double shapeLeftStep = (1 - leftBalance) * step;
1085 320 : double shapeRightStep = (1 - rightBalance) * step;
1086 320 : p3 += PointF(0.0, (shapeLeftStep + shapeRightStep) / 2);
1087 320 : p4 += PointF(0.0, (shapeLeftStep + shapeRightStep) / 2);
1088 : }
1089 1454 : if (collision.right) {
1090 426 : double shapeRightStep = (1 - rightBalance) * step / SHAPE_PREFER_CENTER_FACTOR;
1091 : // Move right Bezier point up(/down) and outwards
1092 426 : p4 += PointF(std::abs(shapeRightStep), shapeRightStep);
1093 : // and a bit also the left point to compensate asymmetry
1094 426 : p3 += PointF(-abs(shapeRightStep), shapeRightStep) / 2.0;
1095 : }
1096 633 : } else if (!slurSeg->isEndPointsEdited()) {
1097 : // In the odd iterations, adjust the end points
1098 : // Slurs steeper than 45° are gently compensated
1099 : static constexpr double STEEP_LIMIT = M_PI / 4;
1100 : // If the collision is in mid region, move more the endpoint which reduces the slur slant
1101 : static constexpr double SLANT_REDUCTION_ANGLE = 5 * M_PI / 180;
1102 : static constexpr double SLANT_REDUCTION_LENGTH = 8;
1103 633 : PointF p2SysCoords = toSystemCoordinates.map(p2);
1104 633 : double curSlant = (p2SysCoords.y() - pp1.y()) / (p2SysCoords.x() - pp1.x());
1105 633 : if (collision.left || collision.mid || slurAngle < -STEEP_LIMIT) {
1106 406 : double endPointLeftStep = leftBalance * step;
1107 140 : if (!collision.left && collision.mid && std::abs(curSlant) > SLANT_REDUCTION_ANGLE
1108 546 : && slurLength > SLANT_REDUCTION_LENGTH) {
1109 45 : double slantReductionRatio = std::abs(curSlant) / SLANT_REDUCTION_ANGLE;
1110 45 : bool leftIsOuter = (slurUp && curSlant > 0) || (!slurUp && curSlant < 0);
1111 45 : endPointLeftStep *= leftIsOuter ? (1 / slantReductionRatio) : slantReductionRatio;
1112 : }
1113 : // Lift the left end point, i.e. tilt the slur around p2
1114 406 : double stepX = sin(slurAngle) * endPointLeftStep;
1115 406 : double stepY = cos(slurAngle) * endPointLeftStep;
1116 406 : PointF pp1delta = PointF(stepX, stepY);
1117 406 : pp1 += PointF(0.0, endPointLeftStep);
1118 406 : p3 += pp1delta * (p2.x() - p3.x()) / p2.x();
1119 406 : p4 += pp1delta * (p2.x() - p4.x()) / p2.x();
1120 : // All points are expressed with respect to pp1, so we need
1121 : // to subtract pp1delta to avoid the whole slur moving up
1122 406 : p2 -= pp1delta;
1123 406 : p3 -= pp1delta;
1124 406 : p4 -= pp1delta;
1125 : }
1126 633 : if (collision.right || collision.mid || slurAngle > STEEP_LIMIT) {
1127 482 : double endPointRightStep = rightBalance * step;
1128 97 : if (!collision.right && collision.mid && std::abs(curSlant) > SLANT_REDUCTION_ANGLE
1129 579 : && slurLength > SLANT_REDUCTION_LENGTH) {
1130 30 : double slantReductionRatio = std::abs(curSlant) / SLANT_REDUCTION_ANGLE;
1131 30 : bool rightIsOuter = (slurUp && curSlant < 0) || (!slurUp && curSlant > 0);
1132 30 : endPointRightStep *= rightIsOuter ? (1 / slantReductionRatio) : slantReductionRatio;
1133 : }
1134 : // Lift the right end point, i.e. tilt the slur around p1
1135 482 : double stepX = sin(slurAngle) * endPointRightStep;
1136 482 : double stepY = cos(slurAngle) * endPointRightStep;
1137 482 : PointF p2delta = PointF(stepX, stepY);
1138 482 : p2 += p2delta;
1139 482 : p3 += p2delta * p3.x() / p2.x();
1140 482 : p4 += p2delta * p4.x() / p2.x();
1141 : }
1142 : }
1143 : // Enforce non-ugliness rules
1144 : // 1) Slur cannot be taller than it is wide
1145 2087 : const double maxRelativeHeight = std::abs(p2.x());
1146 2087 : p3 = slur->up() ? PointF(p3.x(), std::max(p3.y(), -maxRelativeHeight)) : PointF(p3.x(), std::min(p3.y(), maxRelativeHeight));
1147 2087 : p4 = slur->up() ? PointF(p4.x(), std::max(p4.y(), -maxRelativeHeight)) : PointF(p4.x(), std::min(p4.y(), maxRelativeHeight));
1148 : // 2) Tangent rule: p3 and p4 cannot be further left than p1 nor further right than p2
1149 2087 : PointF p3SysCoord = toSystemCoordinates.map(p3);
1150 2087 : PointF p4SysCoord = toSystemCoordinates.map(p4);
1151 2087 : PointF p2SysCoord = toSystemCoordinates.map(p2);
1152 2087 : p3SysCoord = PointF(std::max(pp1.x(), p3SysCoord.x()), p3SysCoord.y());
1153 2087 : p3SysCoord = PointF(std::min(p2SysCoord.x(), p3SysCoord.x()), p3SysCoord.y());
1154 2087 : p4SysCoord = PointF(std::max(pp1.x(), p4SysCoord.x()), p4SysCoord.y());
1155 2087 : p4SysCoord = PointF(std::min(p2SysCoord.x(), p4SysCoord.x()), p4SysCoord.y());
1156 2087 : p3 = toSystemCoordinates.inverted().map(p3SysCoord);
1157 2087 : p4 = toSystemCoordinates.inverted().map(p4SysCoord);
1158 :
1159 2087 : ++iter;
1160 2087 : } while ((collision.left || collision.mid || collision.right) && iter < MAX_ITER);
1161 945 : }
1162 :
1163 915 : Shape SlurTieLayout::getSegmentShapes(SlurSegment* slurSeg, ChordRest* startCR, ChordRest* endCR)
1164 : {
1165 915 : Shape segShapes;
1166 :
1167 : // Determine start and end segments for collision checks
1168 915 : Segment* startSeg = nullptr;
1169 915 : if (slurSeg->isSingleBeginType()) {
1170 901 : if (startCR->isChord() && toChord(startCR)->isGraceAfter()) {
1171 : // if this is a grace-note-after, the shape is stored the *appended* segment
1172 4 : Chord* parent = toChord(startCR->ownershipParent());
1173 4 : if (parent) {
1174 4 : startSeg = parent->graceNotesAfter().appendedSegment();
1175 : }
1176 : } else {
1177 897 : startSeg = startCR->segment(); // first of the slur
1178 : }
1179 : } else {
1180 14 : startSeg = slurSeg->system()->firstMeasure()->findFirstR(SegmentType::ChordRest, Fraction(0, 0)); // first of the system
1181 : }
1182 :
1183 915 : Segment* endSeg = nullptr;
1184 915 : if (slurSeg->isSingleEndType()) {
1185 899 : if (endCR->isChord() && toChord(endCR)->isGraceAfter()) {
1186 : // if this is a grace-note-after, the shape is stored the *appended* segment
1187 17 : Chord* parent = toChord(endCR->ownershipParent());
1188 17 : if (parent) {
1189 17 : endSeg = parent->graceNotesAfter().appendedSegment();
1190 : }
1191 : } else {
1192 882 : endSeg = endCR->segment(); // last of the slur
1193 : }
1194 : } else {
1195 16 : endSeg = slurSeg->system()->lastMeasure()->last(); // last of the system
1196 : }
1197 :
1198 915 : if (!startSeg || !endSeg) {
1199 0 : return segShapes;
1200 : }
1201 :
1202 4412 : for (Segment* seg = startSeg; seg && (seg->isBefore(endSeg) || seg == endSeg); seg = seg->next1enabled()) {
1203 3497 : if (seg->isType(SegmentType::BarLineTypes) || seg->isBreathType() || seg->hasTimeSigAboveStaves() || seg->isTimeTickType()) {
1204 261 : continue;
1205 : }
1206 3236 : segShapes.add(getSegmentShape(slurSeg, seg, startCR, endCR));
1207 : }
1208 :
1209 915 : return segShapes;
1210 0 : }
1211 :
1212 3236 : Shape SlurTieLayout::getSegmentShape(SlurSegment* slurSeg, Segment* seg, ChordRest* startCR, ChordRest* endCR)
1213 : {
1214 3236 : Slur* slur = slurSeg->slur();
1215 3236 : staff_idx_t startStaffIdx = startCR->staffIdx();
1216 3236 : staff_idx_t endStaffIdx = endCR->staffIdx();
1217 3236 : Shape segShape = seg->staffShape(startStaffIdx).translated(seg->pos() + seg->measure()->pos());
1218 :
1219 3236 : bool crossStaffSlur = slur->isCrossStaff() && seg != startCR->segment();
1220 :
1221 : // If cross-staff, also add the shape of second staff
1222 3236 : if (crossStaffSlur) {
1223 140 : endStaffIdx = (endCR->staffIdx() != startStaffIdx) ? endCR->staffIdx() : endCR->vStaffIdx();
1224 140 : SysStaff* startStaff = slurSeg->system()->staves().at(startStaffIdx);
1225 140 : SysStaff* endStaff = slurSeg->system()->staves().at(endStaffIdx);
1226 140 : double dist = endStaff->y() - startStaff->y();
1227 140 : Shape secondStaffShape = seg->staffShape(endStaffIdx).translated(seg->pos() + seg->measure()->pos()); // translate horizontally
1228 140 : secondStaffShape.translate(PointF(0.0, dist)); // translate vertically
1229 140 : segShape.add(secondStaffShape);
1230 140 : }
1231 :
1232 16408 : for (track_idx_t track = staff2track(startStaffIdx); track < staff2track(endStaffIdx, VOICES); ++track) {
1233 13172 : EngravingItem* e = seg->element(track);
1234 13172 : if (!e || !e->isChordRest()) {
1235 9845 : continue;
1236 : }
1237 3327 : if (e->vStaffIdx() != slurSeg->vStaffIdx() && !crossStaffSlur) {
1238 41 : continue;
1239 : }
1240 : // Gets tie and 2 note tremolo shapes
1241 3286 : if (e->isChord()) {
1242 3172 : Chord* chord = toChord(e);
1243 3172 : if (chord->tremoloTwoChord()) {
1244 40 : segShape.add(chord->tremoloTwoChord()->shape());
1245 : }
1246 6562 : for (Note* note : toChord(e)->notes()) {
1247 3390 : Tie* tieFor = note->tieFor();
1248 3390 : Tie* tieBack = note->tieBack();
1249 3390 : if (tieFor && tieFor->up() == slur->up() && !tieFor->segmentsEmpty()) {
1250 73 : TieSegment* tieSegment = tieFor->frontSegment();
1251 73 : if (tieSegment->isSingleBeginType()) {
1252 73 : segShape.add(tieSegment->shape());
1253 : }
1254 : }
1255 3390 : if (tieBack && tieBack->up() == slur->up() && !tieBack->segmentsEmpty()) {
1256 72 : TieSegment* tieSegment = tieBack->backSegment();
1257 72 : if (tieSegment->isEndType()) {
1258 2 : segShape.add(tieSegment->shape());
1259 : }
1260 : }
1261 : }
1262 : }
1263 : }
1264 :
1265 : // Remove items that the slur shouldn't try to avoid
1266 3236 : segShape.remove_if([&](ShapeElement& shapeEl) {
1267 11299 : if (!shapeEl.item() || !shapeEl.item()->ownershipParent() || !shapeEl.item()->visible()) {
1268 104 : return true;
1269 : }
1270 11195 : const EngravingItem* item = shapeEl.item();
1271 11195 : const EngravingObject* parent = item->ownershipParent();
1272 : // Don't remove arpeggio starting on a different voice and ending on the same voice as endCR when slur is on the outside
1273 22390 : if ((item->isArpeggio() || item->isChordBracket()) && (endCR->track() == toArpeggio(item)->endTrack())
1274 0 : && endCR->tick() == item->tick()
1275 22390 : && (!slur->up() && toArpeggio(item)->span() > 1)) {
1276 0 : return false;
1277 : }
1278 :
1279 : // Its own startCR or items belonging to it, lyrics, fingering, ledger lines, articulation on endCR
1280 11195 : if (item == startCR || parent == startCR || item->isTextBase() || item->isLedgerLine()
1281 22390 : || (item->isArticulationFamily() && parent == endCR) || item->isBend() || item->isTappingHalfSlurSegment()) {
1282 3068 : return true;
1283 : }
1284 : // Ornament accidentals on start or end chord
1285 8127 : if (item->isAccidental() && parent->isOrnament()) {
1286 0 : const EngravingObject* parentParent = parent->ownershipParent();
1287 0 : if (parentParent && (parentParent == startCR || parentParent == endCR)) {
1288 0 : return true;
1289 : }
1290 : }
1291 : // Items that are on the start segment but in a different voice
1292 8987 : if ((item->tick() == startCR->tick() && item->track() != startCR->track())
1293 8987 : || (item->tick() == endCR->tick() && item->track() != endCR->track())) {
1294 417 : return true;
1295 : }
1296 : // Edge-case: multiple voices and slur is on the inside
1297 7710 : if (item->vStaffIdx() == startCR->staffIdx()
1298 15132 : && ((!slur->up() && item->track() > startCR->track()) // slur-down: ignore lower voices
1299 7422 : || (slur->up() && item->track() < startCR->track()))) { // slur-up: ignore higher voices
1300 22 : return true;
1301 : }
1302 : // Remove arpeggios spanning more than 1 voice starting on endCR's voice when the slur is on the inside
1303 15376 : if ((item->isArpeggio() || item->isChordBracket())
1304 15376 : && (endCR->track() != item->track() || (!slur->up() && toArpeggio(item)->span() > 1))) {
1305 0 : return true;
1306 : }
1307 : // Ignore big time signatures
1308 7688 : if (item->isTimeSig() && toTimeSig(item)->timeSigPlacement() != TimeSigPlacement::NORMAL) {
1309 0 : return true;
1310 : }
1311 : // Ignore fermatas
1312 7688 : if (item->isFermata()) {
1313 17 : return true;
1314 : }
1315 : // Ignore fret diagrams
1316 7671 : if (item->isFretDiagram()) {
1317 0 : return true;
1318 : }
1319 :
1320 7671 : if (item->isNote() && toNote(item)->shouldHideFret()) {
1321 0 : return true;
1322 : }
1323 :
1324 7671 : return false;
1325 : });
1326 :
1327 3236 : return segShape;
1328 0 : }
1329 :
1330 915 : void SlurTieLayout::addMinClearanceToShapes(Shape& segShapes, double spatium, bool slurUp, const ChordRest* startCR, const ChordRest* endCR)
1331 : {
1332 915 : const double noteClearance = 0.4 * spatium;
1333 915 : const double articulationClearance = 0.20 * spatium;
1334 915 : const double itemClearance = 0.1 * spatium;
1335 :
1336 8586 : for (ShapeElement& shapeEl : segShapes.elements()) {
1337 7671 : const EngravingItem* item = shapeEl.item();
1338 7671 : if (!item || item->parent() == startCR || item->parent() == endCR) {
1339 1780 : continue;
1340 : }
1341 :
1342 5891 : double clearance = 0.0;
1343 5891 : switch (item->type()) {
1344 1541 : case ElementType::NOTE:
1345 1541 : clearance = noteClearance;
1346 1541 : break;
1347 74 : case ElementType::ARTICULATION:
1348 : case ElementType::ORNAMENT:
1349 : case ElementType::TAPPING:
1350 74 : clearance = articulationClearance;
1351 74 : break;
1352 4276 : default:
1353 4276 : clearance = itemClearance;
1354 4276 : break;
1355 : }
1356 :
1357 5891 : slurUp ? shapeEl.adjust(0.0, -clearance, 0.0, 0.0) : shapeEl.adjust(0.0, 0.0, 0.0, clearance);
1358 : }
1359 915 : }
1360 :
1361 885 : double SlurTieLayout::computeArcClearance(double spatium, double slurLength, double slurAngle)
1362 : {
1363 : static constexpr double CLEARANCE_TO_LENGTH_RATIO = 0.04;
1364 : static constexpr double MAX_CLEARANCE = 0.5;
1365 : static constexpr double MIN_CLEARANCE = 0.1;
1366 :
1367 885 : double clearance = CLEARANCE_TO_LENGTH_RATIO * slurLength * std::pow(cos(slurAngle), 2);
1368 885 : clearance = std::clamp(clearance, MIN_CLEARANCE, MAX_CLEARANCE);
1369 :
1370 885 : return clearance * spatium;
1371 : }
1372 :
1373 885 : void SlurTieLayout::computeAdjustmentBalance(SlurSegment* slurSeg, const ChordRest* startCR, const ChordRest* endCR, double& leftBalance,
1374 : double& rightBalance)
1375 : {
1376 885 : Slur* slur = slurSeg->slur();
1377 885 : if (slurSeg->isSingleBeginType() && !slur->stemFloated().left) {
1378 820 : if (slurSeg->isBeginType() || hasArticulationAbove(slurSeg, startCR) || slur->isCrossStaff()) {
1379 30 : leftBalance = 0.1;
1380 : } else {
1381 790 : leftBalance = 0.4;
1382 : }
1383 : } else {
1384 65 : leftBalance = 0.9;
1385 : }
1386 :
1387 885 : if (slurSeg->isSingleEndType() && !slur->stemFloated().right) {
1388 825 : if (slurSeg->isEndType() || hasArticulationAbove(slurSeg, endCR) || slur->isCrossStaff()) {
1389 32 : rightBalance = 0.1;
1390 : } else {
1391 793 : rightBalance = 0.4;
1392 : }
1393 : } else {
1394 60 : rightBalance = 0.9;
1395 : }
1396 885 : }
1397 :
1398 1619 : bool SlurTieLayout::hasArticulationAbove(SlurSegment* slurSeg, const ChordRest* chordRest)
1399 : {
1400 1619 : IF_ASSERT_FAILED(chordRest) {
1401 0 : return false;
1402 : }
1403 :
1404 1619 : if (!chordRest->isChord()) {
1405 12 : return false;
1406 : }
1407 :
1408 1607 : const Chord* chord = toChord(chordRest);
1409 :
1410 1607 : bool slurUp = slurSeg->slur()->up();
1411 1641 : for (const Articulation* artic : chord->articulations()) {
1412 36 : if (!artic->layoutCloseToNote() && artic->up() == slurUp) {
1413 2 : return true;
1414 : }
1415 : }
1416 :
1417 1605 : if (Ornament* ornament = chord->findOrnament()) {
1418 0 : if (ornament->up() == slurUp) {
1419 0 : return true;
1420 : }
1421 : }
1422 :
1423 1605 : return false;
1424 : }
1425 :
1426 885 : double SlurTieLayout::computeAdjustmentStep(int upSign, double spatium, double slurLength)
1427 : {
1428 885 : double step = upSign * 0.30 * spatium;
1429 :
1430 : static constexpr double LONG_SLUR_LIMIT = 16.0; // in spaces
1431 885 : if (slurLength > LONG_SLUR_LIMIT) {
1432 216 : step *= slurLength / LONG_SLUR_LIMIT;
1433 216 : step = std::min(step, 1.5 * spatium);
1434 : }
1435 :
1436 885 : return step;
1437 : }
1438 :
1439 232 : bool SlurTieLayout::stemSideForBeam(Slur* slur, bool start)
1440 : {
1441 : // determines if the anchor point is exempted from the stem inset due to beams or tremolos.
1442 232 : if (!slur) {
1443 0 : return false;
1444 : }
1445 :
1446 232 : ChordRest* cr = start ? slur->startCR() : slur->endCR();
1447 232 : Chord* c = toChord(cr);
1448 232 : bool adjustForBeam = cr && cr->beam() && cr->up() == slur->up();
1449 232 : if (start) {
1450 116 : adjustForBeam = adjustForBeam && cr->beam()->elements().back() != cr;
1451 : } else {
1452 116 : adjustForBeam = adjustForBeam && cr->beam()->elements().front() != cr;
1453 : }
1454 232 : if (adjustForBeam) {
1455 50 : return true;
1456 : }
1457 :
1458 182 : bool adjustForTrem = false;
1459 182 : TremoloTwoChord* trem = c ? c->tremoloTwoChord() : nullptr;
1460 182 : adjustForTrem = trem && trem->up() == slur->up();
1461 182 : if (start) {
1462 91 : adjustForTrem = adjustForTrem && trem->chord2() != c;
1463 : } else {
1464 91 : adjustForTrem = adjustForTrem && trem->chord1() != c;
1465 : }
1466 182 : return adjustForTrem;
1467 : }
1468 :
1469 921 : bool SlurTieLayout::isOverBeams(Slur* slur)
1470 : {
1471 : // returns true if all the chords spanned by the slur are beamed, and all beams are on the same side of the slur
1472 921 : const ChordRest* startCR = slur->startCR();
1473 921 : const ChordRest* endCR = slur->endCR();
1474 921 : if (!startCR || !endCR) {
1475 0 : return false;
1476 : }
1477 921 : if (startCR->track() != endCR->track()
1478 921 : || startCR->tick() >= endCR->tick()) {
1479 81 : return false;
1480 : }
1481 840 : size_t track = startCR->track();
1482 840 : Segment* seg = startCR->segment();
1483 1028 : while (seg && seg->tick() <= endCR->tick()) {
1484 1014 : if (!seg->isChordRestType()
1485 1012 : || !seg->elist().at(track)
1486 2026 : || !seg->elist().at(track)->isChordRest()) {
1487 9 : return false;
1488 : }
1489 1005 : ChordRest* cr = toChordRest(seg->elist().at(track));
1490 1005 : bool hasBeam = cr->beam() && cr->up() == slur->up();
1491 1005 : bool hasTrem = false;
1492 1005 : if (cr->isChord()) {
1493 1005 : Chord* c = toChord(cr);
1494 1005 : hasTrem = c->tremoloTwoChord() && c->up() == slur->up();
1495 : }
1496 1005 : if (!(hasBeam || hasTrem)) {
1497 817 : return false;
1498 : }
1499 188 : if ((!seg->next() || seg->next()->isEndBarLineType()) && seg->measure()->nextMeasure()) {
1500 10 : seg = seg->measure()->nextMeasure()->first();
1501 : } else {
1502 178 : seg = seg->next();
1503 : }
1504 : }
1505 14 : return true;
1506 : }
1507 :
1508 31 : void SlurTieLayout::avoidPreBendsOnTab(const Chord* sc, const Chord* ec, SlurTiePos* sp)
1509 : {
1510 31 : GuitarBend* bendOnStart = nullptr;
1511 31 : GuitarBend* bendOnEnd = nullptr;
1512 31 : if (sc) {
1513 64 : for (Note* note : sc->notes()) {
1514 33 : GuitarBend* bf = note->bendFor();
1515 33 : GuitarBend* bb = note->bendBack();
1516 33 : if (bf && !bf->segmentsEmpty() && bf->bendType() == GuitarBendType::PRE_BEND && !bf->angledPreBend()) {
1517 0 : bendOnStart = bf;
1518 33 : } else if (bb && !bb->segmentsEmpty() && bb->bendType() == GuitarBendType::PRE_BEND && !bb->angledPreBend()) {
1519 0 : bendOnStart = bb;
1520 : }
1521 33 : if (bendOnStart) {
1522 0 : break;
1523 : }
1524 : }
1525 : }
1526 31 : if (ec) {
1527 64 : for (Note* note : ec->notes()) {
1528 33 : GuitarBend* bf = note->bendFor();
1529 33 : GuitarBend* bb = note->bendBack();
1530 33 : if (bf && !bf->segmentsEmpty() && bf->bendType() == GuitarBendType::PRE_BEND && !bf->angledPreBend()) {
1531 0 : bendOnEnd = bf;
1532 33 : } else if (bb && !bb->segmentsEmpty() && bb->bendType() == GuitarBendType::PRE_BEND && !bb->angledPreBend()) {
1533 0 : bendOnEnd = bb;
1534 : }
1535 33 : if (bendOnEnd) {
1536 0 : break;
1537 : }
1538 : }
1539 : }
1540 :
1541 31 : if (bendOnStart) {
1542 0 : sp->p1.rx() = std::max(sp->p1.x(), bendOnStart->frontSegment()->pos().x() + 0.33 * sc->spatium());
1543 : }
1544 31 : if (bendOnEnd) {
1545 0 : sp->p2.rx() = std::min(sp->p2.x(), bendOnEnd->frontSegment()->pos().x() - 0.33 * ec->spatium());
1546 : }
1547 31 : }
1548 :
1549 5378 : bool SlurTieLayout::tieSegmentShouldBeSkipped(const Tie* item)
1550 : {
1551 5378 : Note* startNote = item->startNote();
1552 5378 : StaffType* st = item->staff()->staffType(startNote ? startNote->tick() : Fraction(0, 1));
1553 5378 : if (!st || !st->isTabStaff()) {
1554 5325 : return false;
1555 : }
1556 :
1557 53 : if (startNote && startNote->isContinuationOfBend()) {
1558 4 : return true;
1559 : }
1560 :
1561 49 : ShowTiedFret showTiedFret = item->style().value(Sid::tabShowTiedFret).value<ShowTiedFret>();
1562 :
1563 49 : return showTiedFret == ShowTiedFret::NONE;
1564 : }
1565 :
1566 4462 : TieSegment* SlurTieLayout::layoutTieFor(Tie* item, System* system)
1567 : {
1568 4462 : item->setPos(0, 0);
1569 :
1570 4462 : if (!item->startNote()) {
1571 0 : LOGD("no start note");
1572 0 : return nullptr;
1573 : }
1574 :
1575 : // do not layout ties in tablature if not showing back-tied fret marks
1576 4462 : if (tieSegmentShouldBeSkipped(item)) {
1577 4 : if (!item->segmentsEmpty()) {
1578 0 : item->eraseSpannerSegments();
1579 : }
1580 :
1581 4 : return nullptr;
1582 : }
1583 :
1584 4458 : if (item->isPartialTie()) {
1585 110 : return layoutPartialTie(toPartialTie(item));
1586 : }
1587 :
1588 4348 : calculateDirection(item);
1589 4348 : calculateIsInside(item);
1590 :
1591 4348 : SlurTiePos sPos;
1592 4348 : sPos.p1 = computeDefaultStartOrEndPoint(item, Grip::START);
1593 :
1594 4348 : computeStartAndEndSystem(item, sPos);
1595 :
1596 4348 : int segmentCount = sPos.system1 == sPos.system2 ? 1 : 2;
1597 4348 : if (segmentCount == 2) {
1598 568 : sPos.p2 = PointF(endingXForOpenEndedSlurTie(system->lastMeasure(), item->startNote()->vStaffIdx()), sPos.p1.y());
1599 : } else {
1600 3780 : sPos.p2 = computeDefaultStartOrEndPoint(item, Grip::END);
1601 : }
1602 :
1603 4348 : correctForCrossStaff(item, sPos, sPos.system1 != sPos.system2 ? SpannerSegmentType::BEGIN : SpannerSegmentType::SINGLE);
1604 4348 : forceHorizontal(item, sPos);
1605 :
1606 4348 : item->fixupSegments(segmentCount);
1607 4348 : TieSegment* segment = item->segmentAt(0);
1608 4348 : segment->setTrack(item->track());
1609 4348 : segment->setSpannerSegmentType(sPos.system1 != sPos.system2 ? SpannerSegmentType::BEGIN : SpannerSegmentType::SINGLE);
1610 4348 : segment->moveToSystem(system); // Needed to populate System.spannerSegments
1611 4348 : segment->resetAdjustmentOffset();
1612 4348 : segment->mutldata()->allJumpPointsInactive = item->allJumpPointsInactive();
1613 :
1614 4348 : const Chord* startChord = item->startNote()->chord();
1615 4348 : item->setTick(startChord->tick()); // Why is this here?? (M.S.)
1616 :
1617 4348 : if (segment->autoplace() && !segment->isEdited()) {
1618 4348 : adjustX(segment, sPos, Grip::START);
1619 4348 : if (segment->isSingleType()) {
1620 3780 : adjustX(segment, sPos, Grip::END);
1621 : }
1622 : }
1623 :
1624 4348 : adjustYforLedgerLines(segment, sPos);
1625 :
1626 4348 : segment->ups(Grip::START).p = sPos.p1;
1627 4348 : segment->ups(Grip::END).p = sPos.p2;
1628 :
1629 4348 : if (segment->autoplace() && !segment->isEdited()) {
1630 4348 : adjustY(segment);
1631 : } else {
1632 0 : computeBezier(segment);
1633 : }
1634 :
1635 4348 : addLineAttachPoints(segment); // add attach points to start and end note
1636 4348 : return segment;
1637 : }
1638 :
1639 819 : TieSegment* SlurTieLayout::layoutTieBack(Tie* item, System* system, LayoutContext& ctx)
1640 : {
1641 819 : Chord* chord = item->endNote() ? item->endNote()->chord() : nullptr;
1642 :
1643 819 : if (item->staffType() && item->staffType()->isTabStaff()) {
1644 : // On TAB, the presence of this tie may require to add a parenthesis
1645 10 : ChordLayout::layout(chord, ctx);
1646 : }
1647 : // do not layout ties in tablature if not showing back-tied fret marks
1648 819 : if (tieSegmentShouldBeSkipped(item)) {
1649 0 : if (!item->segmentsEmpty()) {
1650 0 : item->eraseSpannerSegments();
1651 : }
1652 :
1653 0 : return nullptr;
1654 : }
1655 :
1656 819 : if (item->isPartialTie()) {
1657 230 : return layoutPartialTie(toPartialTie(item));
1658 : }
1659 :
1660 589 : SlurTiePos sPos;
1661 589 : computeStartAndEndSystem(item, sPos);
1662 589 : sPos.p2 = computeDefaultStartOrEndPoint(item, Grip::END);
1663 :
1664 589 : double x = system ? system->firstNoteRestSegmentX(true) : 0;
1665 589 : double y = sPos.p2.y();
1666 589 : sPos.p1 = PointF(x, y);
1667 :
1668 589 : item->fixupSegments(2);
1669 589 : TieSegment* segment = item->segmentAt(1);
1670 589 : segment->setTrack(item->track());
1671 589 : segment->moveToSystem(system);
1672 589 : segment->resetAdjustmentOffset();
1673 :
1674 589 : if (chord) {
1675 589 : segment->setStaffMove(static_cast<int>(chord->vStaffIdx() - segment->staffIdx()));
1676 : }
1677 :
1678 589 : adjustY(segment);
1679 589 : segment->setSpannerSegmentType(SpannerSegmentType::END);
1680 :
1681 589 : if (segment->autoplace() && !segment->isEdited()) {
1682 589 : adjustX(segment, sPos, Grip::END);
1683 : }
1684 :
1685 589 : adjustYforLedgerLines(segment, sPos);
1686 :
1687 589 : segment->ups(Grip::START).p = sPos.p1;
1688 589 : segment->ups(Grip::END).p = sPos.p2;
1689 :
1690 589 : if (segment->autoplace() && !segment->isEdited()) {
1691 589 : adjustY(segment);
1692 : } else {
1693 0 : computeBezier(segment);
1694 : }
1695 :
1696 589 : addLineAttachPoints(segment);
1697 589 : return segment;
1698 : }
1699 :
1700 5351 : void SlurTieLayout::computeStartAndEndSystem(Tie* item, SlurTiePos& slurTiePos)
1701 : {
1702 5351 : Chord* startChord = item->startNote() ? item->startNote()->chord() : nullptr;
1703 5351 : Chord* endChord = item->endNote() ? item->endNote()->chord() : nullptr;
1704 :
1705 5351 : System* startSystem = startChord ? startChord->measure()->system() : nullptr;
1706 :
1707 5351 : System* endSystem = endChord ? endChord->measure()->system() : startSystem;
1708 :
1709 5351 : if (!startSystem && !endSystem) {
1710 0 : if (startChord) {
1711 0 : Measure* m = startChord->measure();
1712 0 : LOGD("No system: measure is %d has %d count %d", m->isMMRest(), m->hasMMRest(), m->mmRestCount());
1713 : } else {
1714 0 : LOGD("No start or end system for tie at ") << item->tick().toString();
1715 : }
1716 : }
1717 :
1718 5351 : slurTiePos.system1 = startSystem;
1719 5351 : slurTiePos.system2 = endSystem;
1720 5351 : }
1721 :
1722 9131 : PointF SlurTieLayout::computeDefaultStartOrEndPoint(const Tie* tie, Grip startOrEnd)
1723 : {
1724 9131 : if (startOrEnd != Grip::START && startOrEnd != Grip::END) {
1725 0 : return PointF();
1726 : }
1727 :
1728 9131 : bool start = startOrEnd == Grip::START;
1729 :
1730 9131 : Note* note = start ? tie->startNote() : tie->endNote();
1731 9131 : Chord* chord = note ? note->chord() : nullptr;
1732 :
1733 9131 : if (!chord) {
1734 0 : return PointF();
1735 : }
1736 :
1737 9131 : PointF result = note->pos() + chord->pos() + chord->segment()->pos() + chord->measure()->pos();
1738 :
1739 9131 : const bool up = tie->up();
1740 9131 : const bool inside = tie->isInside();
1741 9131 : const bool noteIsHiddenFret = note->shouldHideFret();
1742 9131 : const int upSign = up ? -1 : 1;
1743 9131 : const int leftRightSign = start ? +1 : -1;
1744 9131 : const double noteWidth = note->width();
1745 9131 : const double noteHeight = note->height();
1746 9131 : const double spatium = tie->spatium();
1747 :
1748 9131 : double baseX = (inside && !noteIsHiddenFret) ? (start ? noteWidth : 0.0) : noteOpticalCenterForTie(note, up);
1749 9131 : double baseY = inside ? 0.0 : upSign * noteHeight / 2;
1750 :
1751 9131 : result += PointF(baseX, baseY);
1752 :
1753 9131 : double visualInsetSp = 0.0;
1754 9131 : if (inside || note->headGroup() == NoteHeadGroup::HEAD_SLASH || noteIsHiddenFret) {
1755 24 : visualInsetSp = 0.2;
1756 9107 : } else if (note->hasAnotherStraightAboveOrBelow(up)) {
1757 386 : visualInsetSp = 0.45;
1758 : } else {
1759 8721 : visualInsetSp = 0.1;
1760 : }
1761 :
1762 9131 : double visualInset = visualInsetSp * spatium * leftRightSign;
1763 9131 : const double yOffset = 0.20 * spatium * upSign; // TODO: style
1764 :
1765 9131 : result += PointF(visualInset, yOffset);
1766 :
1767 9131 : return result;
1768 : }
1769 :
1770 9131 : double SlurTieLayout::noteOpticalCenterForTie(const Note* note, bool up)
1771 : {
1772 9131 : if (note->headGroup() == NoteHeadGroup::HEAD_SLASH) {
1773 24 : double singleSlashWidth = note->symBbox(SymId::noteheadSlashHorizontalEnds).width();
1774 24 : double noteWidth = note->headWidth();
1775 24 : double center = 0.20 * note->spatium() + 0.5 * (noteWidth - singleSlashWidth);
1776 24 : return up ? note->ldata()->bbox().right() - center : note->ldata()->bbox().left() + center;
1777 : }
1778 9107 : SymId symId = note->ldata()->cachedNoteheadSym.value();
1779 9107 : PointF cutOutLeft = note->symSmuflAnchor(symId, up ? SmuflAnchorId::cutOutNW : SmuflAnchorId::cutOutSW);
1780 9107 : PointF cutOutRight = note->symSmuflAnchor(symId, up ? SmuflAnchorId::cutOutNE : SmuflAnchorId::cutOutSE);
1781 :
1782 9107 : if (cutOutLeft.isNull() || cutOutRight.isNull()) {
1783 5959 : return 0.5 * note->width();
1784 : }
1785 :
1786 3148 : return 0.5 * (cutOutLeft.x() + cutOutRight.x());
1787 : }
1788 :
1789 0 : void SlurTieLayout::createSlurSegments(Slur* item, LayoutContext& ctx)
1790 : {
1791 0 : const ChordRest* startCR = item->startCR();
1792 0 : const ChordRest* endCR = item->endCR();
1793 0 : assert(startCR && endCR);
1794 :
1795 0 : const System* startSys = startCR->measure()->system();
1796 0 : const System* endSys = endCR->measure()->system();
1797 :
1798 0 : const std::vector<System*>& systems = ctx.dom().systems();
1799 0 : system_idx_t startSysIdx = muse::indexOf(systems, startSys);
1800 0 : system_idx_t endSysIdx = muse::indexOf(systems, endSys);
1801 0 : if (startSysIdx == muse::nidx || endSysIdx == muse::nidx) {
1802 0 : return;
1803 : }
1804 :
1805 0 : int segmentsNeeded = 0;
1806 0 : for (system_idx_t i = startSysIdx; i <= endSysIdx; ++i) {
1807 0 : if (systems.at(i)->vbox()) {
1808 0 : continue;
1809 : }
1810 0 : ++segmentsNeeded;
1811 : }
1812 0 : int currentSegments = int(item->spannerSegments().size());
1813 :
1814 0 : if (currentSegments != segmentsNeeded) {
1815 0 : item->fixupSegments(segmentsNeeded);
1816 : }
1817 :
1818 0 : int segIdx = 0;
1819 0 : for (system_idx_t i = startSysIdx; i <= endSysIdx; ++i) {
1820 0 : System* system = systems.at(i);
1821 0 : if (system->vbox()) {
1822 0 : continue;
1823 : }
1824 0 : SlurSegment* lineSegm = item->segmentAt(segIdx++);
1825 0 : lineSegm->moveToSystem(system);
1826 0 : if (startSysIdx == endSysIdx) {
1827 0 : lineSegm->setSpannerSegmentType(SpannerSegmentType::SINGLE);
1828 0 : } else if (i == startSysIdx) {
1829 0 : lineSegm->setSpannerSegmentType(SpannerSegmentType::BEGIN);
1830 0 : } else if (i > 0 && i != endSysIdx) {
1831 0 : lineSegm->setSpannerSegmentType(SpannerSegmentType::MIDDLE);
1832 0 : } else if (i == endSysIdx) {
1833 0 : lineSegm->setSpannerSegmentType(SpannerSegmentType::END);
1834 : }
1835 : }
1836 : }
1837 :
1838 385 : void SlurTieLayout::adjustOverlappingSlurs(const std::list<SpannerSegment*>& spannerSegments)
1839 : {
1840 385 : std::vector<SlurSegment*> segments;
1841 1647 : for (SpannerSegment* seg : spannerSegments) {
1842 1262 : if (seg->isSlurSegment()) {
1843 871 : segments.push_back(toSlurSegment(seg));
1844 : }
1845 : }
1846 385 : if (segments.size() <= 1) {
1847 205 : return;
1848 : }
1849 :
1850 : //how far vertically an endpoint should adjust to avoid other slur endpoints:
1851 180 : const double spatium = segments.front()->spatium();
1852 180 : const double slurCollisionVertOffset = 0.65 * spatium;
1853 180 : const double slurCollisionHorizOffset = 0.2 * spatium;
1854 180 : const double fuzzyHorizCompare = 0.25 * spatium;
1855 179 : auto compare = [fuzzyHorizCompare](double x1, double x2) { return std::abs(x1 - x2) < fuzzyHorizCompare; };
1856 846 : for (SlurSegment* slur1 : segments) {
1857 4140 : for (SlurSegment* slur2 : segments) {
1858 3474 : if (slur2 == slur1) {
1859 666 : continue;
1860 : }
1861 2808 : if (slur1->slur()->endChord() == slur2->slur()->startChord()
1862 2808 : && compare(slur1->ups(Grip::END).p.y(), slur2->ups(Grip::START).p.y())) {
1863 8 : slur1->ups(Grip::END).p.rx() -= slurCollisionHorizOffset;
1864 8 : slur2->ups(Grip::START).p.rx() += slurCollisionHorizOffset;
1865 8 : SlurTieLayout::computeBezier(slur1);
1866 8 : SlurTieLayout::computeBezier(slur2);
1867 8 : continue;
1868 : }
1869 :
1870 2800 : SlurTieSegment* slurTie2 = toSlurTieSegment(slur2);
1871 :
1872 : // slurs don't collide with themselves or slurs on other staves
1873 2800 : if (slur1->vStaffIdx() != slurTie2->vStaffIdx()) {
1874 922 : continue;
1875 : }
1876 : // slurs which don't overlap don't need to be checked
1877 1878 : if (slur1->ups(Grip::END).p.x() < slurTie2->ups(Grip::START).p.x()
1878 1015 : || slurTie2->ups(Grip::END).p.x() < slur1->ups(Grip::START).p.x()
1879 2893 : || slur1->slur()->up() != slurTie2->slurTie()->up()) {
1880 1800 : continue;
1881 : }
1882 : // START POINT
1883 78 : if (compare(slur1->ups(Grip::START).p.x(), slurTie2->ups(Grip::START).p.x())) {
1884 28 : if (slur1->ups(Grip::END).p.x() > slurTie2->ups(Grip::END).p.x() || slurTie2->isTieSegment()) {
1885 : // slur1 is the "outside" slur
1886 5 : slur1->ups(Grip::START).p.ry() += slurCollisionVertOffset * (slur1->slur()->up() ? -1 : 1);
1887 5 : SlurTieLayout::computeBezier(slur1);
1888 : }
1889 : }
1890 : // END POINT
1891 78 : if (compare(slur1->ups(Grip::END).p.x(), slurTie2->ups(Grip::END).p.x())) {
1892 : // slurs have the same endpoint
1893 24 : if (slur1->ups(Grip::START).p.x() < slurTie2->ups(Grip::START).p.x() || slurTie2->isTieSegment()) {
1894 : // slur1 is the "outside" slur
1895 3 : slur1->ups(Grip::END).p.ry() += slurCollisionVertOffset * (slur1->slur()->up() ? -1 : 1);
1896 3 : SlurTieLayout::computeBezier(slur1);
1897 : }
1898 : }
1899 : }
1900 : }
1901 385 : }
1902 :
1903 74 : LaissezVibSegment* SlurTieLayout::createLaissezVibSegment(LaissezVib* item)
1904 : {
1905 74 : Chord* startChord = item->startNote()->chord();
1906 74 : item->setTick(startChord->tick());
1907 :
1908 74 : calculateDirection(item);
1909 74 : calculateIsInside(item);
1910 :
1911 74 : item->fixupSegments(1);
1912 74 : LaissezVibSegment* segment = item->segmentAt(0);
1913 74 : segment->setSpannerSegmentType(SpannerSegmentType::SINGLE);
1914 74 : segment->setTrack(item->track());
1915 74 : segment->moveToSystem(item->startNote()->chord()->segment()->measure()->system());
1916 74 : segment->resetAdjustmentOffset();
1917 :
1918 74 : return segment;
1919 : }
1920 :
1921 74 : void SlurTieLayout::calculateLaissezVibX(LaissezVibSegment* segment, SlurTiePos& sPos, bool smufl)
1922 : {
1923 74 : LaissezVib* lv = segment->laissezVib();
1924 :
1925 74 : computeStartAndEndSystem(lv, sPos);
1926 74 : sPos.p1 = computeDefaultStartOrEndPoint(lv, Grip::START);
1927 :
1928 74 : correctForCrossStaff(lv, sPos, SpannerSegmentType::SINGLE);
1929 :
1930 74 : if (segment->autoplace() && !segment->isEdited()) {
1931 74 : adjustX(segment, sPos, Grip::START);
1932 : }
1933 :
1934 74 : if (smufl) {
1935 0 : LaissezVibSegment::LayoutData* ldata = segment->mutldata();
1936 0 : ldata->symbol = lv->symId();
1937 0 : ldata->setBbox(segment->symBbox(ldata->symbol));
1938 0 : ldata->setShape(Shape(ldata->bbox(), segment));
1939 0 : ldata->setMag(segment->laissezVib()->startNote()->mag());
1940 : }
1941 :
1942 74 : const double width = smufl ? segment->width() : lv->absoluteFromSpatium(lv->minLength());
1943 :
1944 74 : sPos.p2 = PointF(sPos.p1.x() + width, sPos.p1.y());
1945 74 : }
1946 :
1947 74 : void SlurTieLayout::calculateLaissezVibY(LaissezVibSegment* segment, SlurTiePos& sPos)
1948 : {
1949 74 : LaissezVib* lv = segment->laissezVib();
1950 :
1951 74 : adjustYforLedgerLines(segment, sPos);
1952 :
1953 74 : Note* note = lv->startNote();
1954 74 : Chord* chord = note->chord();
1955 :
1956 74 : const NoteParenthesisInfo* noteParenInfo = chord->findNoteParenthesisInfo(note);
1957 :
1958 74 : Parenthesis* paren = nullptr;
1959 74 : if (noteParenInfo) {
1960 0 : paren = noteParenInfo->leftParen();
1961 : }
1962 :
1963 74 : const bool avoidStem = chord->stem() && chord->stem()->visible() && chord->up() == lv->up();
1964 74 : if (paren && (!lv->isOuterTieOfChord(Grip::START) || avoidStem)) {
1965 0 : RectF parenBbox = paren->ldata()->bbox().translated(paren->systemPos());
1966 0 : double adj = sPos.p1.y() - (lv->up() ? parenBbox.top() : parenBbox.bottom());
1967 0 : const double PAREN_LV_PADDING = 0.1 * segment->spatium();
1968 0 : adj += PAREN_LV_PADDING * (lv->up() ? 1.0 : -1.0);
1969 0 : sPos.p1.ry() -= adj;
1970 0 : sPos.p2.ry() -= adj;
1971 : }
1972 :
1973 74 : segment->ups(Grip::START).p = sPos.p1;
1974 74 : segment->ups(Grip::END).p = sPos.p2;
1975 :
1976 74 : if (segment->autoplace() && !segment->isEdited()) {
1977 74 : adjustY(segment);
1978 : } else {
1979 0 : computeBezier(segment);
1980 : }
1981 74 : }
1982 :
1983 340 : PartialTieSegment* SlurTieLayout::createPartialTieSegment(PartialTie* item)
1984 : {
1985 340 : Chord* chord = item->isOutgoing() ? item->startNote()->chord() : item->endNote()->chord();
1986 340 : item->setTick(chord->tick());
1987 :
1988 340 : calculateDirection(item);
1989 340 : calculateIsInside(item);
1990 :
1991 340 : item->fixupSegments(1);
1992 340 : PartialTieSegment* segment = item->segmentAt(0);
1993 340 : segment->setSpannerSegmentType(SpannerSegmentType::SINGLE);
1994 340 : segment->moveToSystem(chord->segment()->measure()->system());
1995 340 : segment->setTrack(item->track());
1996 340 : segment->resetAdjustmentOffset();
1997 340 : segment->mutldata()->allJumpPointsInactive = item->allJumpPointsInactive();
1998 :
1999 340 : return segment;
2000 : }
2001 :
2002 340 : PartialTieSegment* SlurTieLayout::layoutPartialTie(PartialTie* item)
2003 : {
2004 340 : const bool outgoing = item->isOutgoing();
2005 340 : PartialTieSegment* segment = createPartialTieSegment(item);
2006 340 : SlurTiePos sPos;
2007 :
2008 340 : computeStartAndEndSystem(item, sPos);
2009 340 : if (outgoing) {
2010 110 : sPos.p1 = computeDefaultStartOrEndPoint(item, Grip::START);
2011 : } else {
2012 230 : sPos.p2 = computeDefaultStartOrEndPoint(item, Grip::END);
2013 : }
2014 :
2015 340 : if (segment->autoplace() && !segment->isEdited()) {
2016 340 : adjustX(segment, sPos, outgoing ? Grip::START : Grip::END);
2017 : }
2018 :
2019 340 : setPartialTieEndPos(item, sPos);
2020 :
2021 340 : correctForCrossStaff(item, sPos, SpannerSegmentType::SINGLE);
2022 :
2023 340 : adjustYforLedgerLines(segment, sPos);
2024 :
2025 340 : segment->ups(Grip::START).p = sPos.p1;
2026 340 : segment->ups(Grip::END).p = sPos.p2;
2027 :
2028 340 : if (segment->autoplace() && !segment->isEdited()) {
2029 340 : adjustY(segment);
2030 : } else {
2031 0 : computeBezier(segment);
2032 : }
2033 :
2034 340 : addLineAttachPoints(segment);
2035 :
2036 340 : return segment;
2037 : }
2038 :
2039 340 : void SlurTieLayout::setPartialTieEndPos(PartialTie* item, SlurTiePos& sPos)
2040 : {
2041 340 : const bool outgoing = item->isOutgoing();
2042 :
2043 340 : const Chord* chord = item->parentNote()->chord();
2044 340 : const Segment* seg = chord->segment();
2045 340 : const Measure* measure = seg->measure();
2046 340 : const System* system = measure->system();
2047 :
2048 340 : if (seg->measure()->isFirstInSystem() && !outgoing) {
2049 98 : sPos.p1 = PointF((system ? system->firstNoteRestSegmentX(true) : 0), sPos.p2.y());
2050 98 : return;
2051 : }
2052 :
2053 243 : auto shouldSkipSegment = [](const Segment* adjSeg, staff_idx_t staff) {
2054 482 : bool inactiveOrInvisible = !adjSeg->isActive() || !adjSeg->enabled() || adjSeg->allElementsInvisible()
2055 482 : || !adjSeg->hasElements(staff);
2056 243 : bool isAboveStaff = adjSeg->isBreathType() || adjSeg->hasTimeSigAboveStaves();
2057 243 : return inactiveOrInvisible || isAboveStaff;
2058 : };
2059 :
2060 242 : const Segment* adjSeg = outgoing ? seg->next1() : seg->prev1();
2061 246 : while (adjSeg && shouldSkipSegment(adjSeg, item->vStaffIdx())) {
2062 4 : adjSeg = outgoing ? adjSeg->next1() : adjSeg->prev1();
2063 : }
2064 :
2065 242 : double widthToSegment = 0.0;
2066 242 : if (adjSeg) {
2067 239 : EngravingItem* element = adjSeg->element(staff2track(item->vStaffIdx()));
2068 239 : double elPos = 0.0;
2069 239 : double elementWidth = 0.0;
2070 239 : if (element) {
2071 239 : const Shape& elemShape = element->shape();
2072 239 : elPos = element->pos().x() + elemShape.bbox().x();
2073 239 : elementWidth = elemShape.bbox().width();
2074 239 : }
2075 :
2076 239 : const double elPosInSystemCoords = adjSeg->xPosInSystemCoords() + elPos;
2077 239 : widthToSegment = outgoing ? elPosInSystemCoords - sPos.p1.x() : sPos.p2.x() - (elPosInSystemCoords + elementWidth);
2078 132 : bool incomingFromBarline = !outgoing && element && element->isBarLine()
2079 371 : && toBarLine(element)->barLineType() != BarLineType::START_REPEAT;
2080 : widthToSegment
2081 239 : -= item->style().styleAbsolute(incomingFromBarline ? Sid::barlineToLineStartDistance : Sid::lineEndToBarlineDistance);
2082 : }
2083 :
2084 242 : if (outgoing) {
2085 110 : sPos.p2 = PointF(sPos.p1.x() + widthToSegment, sPos.p1.y());
2086 : } else {
2087 132 : sPos.p1 = PointF(sPos.p2.x() - widthToSegment, sPos.p2.y());
2088 : }
2089 : }
2090 :
2091 101714 : void SlurTieLayout::layoutLaissezVibChord(Chord* chord, LayoutContext& ctx)
2092 : {
2093 : // Laissez vib ties should all end at the same rightmost point while honouring each tie's minimum length
2094 : // Ties drawn by MuseScore can start at differing points
2095 : // SMuFL symbols will also start at the same point because they are of fixed length
2096 101714 : double chordLvEndPoint = -DBL_MAX;
2097 101714 : std::map<LaissezVibSegment*, SlurTiePos> lvSegmentsWithPositions;
2098 101714 : const bool smuflLayout = ctx.conf().styleB(Sid::laissezVibUseSmuflSym);
2099 :
2100 221854 : for (const Note* note : chord->notes()) {
2101 120140 : LaissezVib* lv = note->laissezVib();
2102 120140 : if (!lv) {
2103 120066 : continue;
2104 : }
2105 74 : SlurTiePos sPos;
2106 74 : LaissezVibSegment* lvSeg = createLaissezVibSegment(lv);
2107 :
2108 74 : calculateLaissezVibX(lvSeg, sPos, smuflLayout);
2109 :
2110 74 : chordLvEndPoint = std::max(chordLvEndPoint, sPos.p2.x());
2111 :
2112 74 : lvSegmentsWithPositions.insert({ lvSeg, sPos });
2113 : }
2114 :
2115 101788 : for (auto& segWithPos : lvSegmentsWithPositions) {
2116 74 : LaissezVibSegment* lvSeg = segWithPos.first;
2117 74 : Note* note = lvSeg->laissezVib()->startNote();
2118 74 : SlurTiePos sPos = segWithPos.second;
2119 74 : const double xDiff = chordLvEndPoint - sPos.p2.x();
2120 74 : sPos.p2.setX(chordLvEndPoint);
2121 74 : if (smuflLayout) {
2122 0 : sPos.p1.setX(sPos.p1.x() + xDiff);
2123 : }
2124 :
2125 74 : calculateLaissezVibY(lvSeg, sPos);
2126 :
2127 74 : LaissezVibSegment::LayoutData* ldata = lvSeg->mutldata();
2128 74 : if (smuflLayout) {
2129 0 : ldata->setBbox(lvSeg->symBbox(ldata->symbol));
2130 0 : ldata->setShape(Shape(ldata->bbox(), lvSeg));
2131 0 : ldata->setPos(sPos.p1);
2132 : }
2133 :
2134 74 : double yOrigin = sPos.system1->staff(chord->staffIdx())->y();
2135 74 : double yMoved = sPos.system1->staff(chord->vStaffIdx())->y();
2136 74 : double yDiff = yMoved - yOrigin;
2137 :
2138 74 : const PointF chordPos = chord->pos() + chord->segment()->pos() + chord->measure()->pos() + PointF(0.0, yDiff);
2139 74 : const PointF notePos = chordPos + note->pos();
2140 74 : ldata->posRelativeToNote = sPos.p1 - notePos;
2141 :
2142 74 : TLayout::fillNoteShape(note, note->mutldata());
2143 : }
2144 101714 : }
2145 :
2146 4762 : void SlurTieLayout::correctForCrossStaff(Tie* tie, SlurTiePos& sPos, SpannerSegmentType type)
2147 : {
2148 4762 : Chord* startChord = tie->startNote() ? tie->startNote()->chord() : nullptr;
2149 4762 : Chord* endChord = tie->endNote() ? tie->endNote()->chord() : nullptr;
2150 :
2151 4762 : if (!startChord) {
2152 16 : return;
2153 : }
2154 :
2155 4746 : bool startStaffDiff = startChord->vStaffIdx() != tie->staffIdx();
2156 4746 : bool endStaffDiff = endChord ? endChord->vStaffIdx() != tie->staffIdx() : false;
2157 4746 : double curY1 = sPos.p1.y();
2158 4746 : double curY2 = sPos.p2.y();
2159 :
2160 4746 : if (type == SpannerSegmentType::BEGIN) {
2161 : // Cross-system start tie
2162 568 : if (startStaffDiff && sPos.system1) {
2163 0 : double yOrigin = sPos.system1->staff(tie->staffIdx())->y();
2164 0 : double yMoved = sPos.system1->staff(startChord->vStaffIdx())->y();
2165 0 : double yDiff = yMoved - yOrigin;
2166 0 : sPos.p1.setY(curY1 + yDiff);
2167 0 : sPos.p2.setY(curY2 + yDiff);
2168 : }
2169 4178 : } else if (type == SpannerSegmentType::SINGLE) {
2170 : // Same system single tie
2171 4178 : if (startStaffDiff && sPos.system1) {
2172 0 : double yOrigin = sPos.system1->staff(tie->staffIdx())->y();
2173 0 : double yMoved = sPos.system1->staff(startChord->vStaffIdx())->y();
2174 0 : double yDiff = yMoved - yOrigin;
2175 0 : sPos.p1.setY(curY1 + yDiff);
2176 0 : if (tie->isLaissezVib()) {
2177 0 : sPos.p2.setY(curY2 + yDiff);
2178 0 : return;
2179 : }
2180 : }
2181 4178 : if (!endChord) {
2182 184 : return;
2183 : }
2184 3994 : if (endStaffDiff && sPos.system2) {
2185 6 : double yOrigin = sPos.system2->staff(tie->staffIdx())->y();
2186 6 : double yMoved = sPos.system2->staff(endChord->vStaffIdx())->y();
2187 6 : double yDiff = yMoved - yOrigin;
2188 6 : sPos.p2.setY(curY2 + yDiff);
2189 : }
2190 : }
2191 : }
2192 :
2193 4348 : void SlurTieLayout::forceHorizontal(Tie* tie, SlurTiePos& sPos)
2194 : {
2195 4348 : Note* startNote = tie->startNote();
2196 4348 : Note* endNote = tie->endNote();
2197 :
2198 4348 : if (startNote && endNote
2199 4348 : && startNote->line() == endNote->line()
2200 8696 : && startNote->vStaffIdx() == endNote->vStaffIdx()) {
2201 4012 : double y1 = sPos.p1.y();
2202 4012 : double y2 = sPos.p2.y();
2203 4012 : double outerY = tie->up() ? std::min(y1, y2) : std::max(y1, y2);
2204 4012 : sPos.p1.setY(outerY);
2205 4012 : sPos.p2.setY(outerY);
2206 : }
2207 4348 : }
2208 :
2209 9131 : void SlurTieLayout::adjustX(TieSegment* tieSegment, SlurTiePos& sPos, Grip startOrEnd)
2210 : {
2211 9131 : bool start = startOrEnd == Grip::START;
2212 :
2213 9131 : PointF& tiePoint = start ? sPos.p1 : sPos.p2;
2214 9131 : double resultingX = tiePoint.x();
2215 :
2216 9131 : Tie* tie = tieSegment->tie();
2217 9131 : Note* note = start ? tie->startNote() : tie->endNote();
2218 9131 : const Note* otherNote = start ? tie->endNote() : tie->startNote();
2219 9131 : if (!note || note->shouldHideFret()) {
2220 7547 : return;
2221 : }
2222 :
2223 9131 : Chord* chord = note->chord();
2224 9131 : const Chord* otherChord = otherNote ? otherNote->chord() : nullptr;
2225 9131 : const Segment* chordSeg = chord->segment();
2226 9131 : const double spatium = tieSegment->spatium();
2227 9131 : const double padding = 0.20 * spatium * (start ? 1 : -1); // TODO: style
2228 9131 : const bool isGrace = (chord && chord->isGrace())
2229 18262 : || ((otherChord && otherChord->isGrace()) && chord->segment() == otherChord->segment());
2230 :
2231 9131 : PointF systemPos = chordSeg->pos() + chord->measure()->pos() + (isGrace ? chord->pos() : PointF());
2232 9131 : if (chord->vStaffIdx() != tieSegment->vStaffIdx()) {
2233 6 : System* system = tieSegment->system();
2234 6 : double yDiff = system->staff(chord->vStaffIdx())->y() - system->staff(tie->staffIdx())->y();
2235 6 : systemPos += PointF(0.0, yDiff);
2236 : }
2237 :
2238 9131 : const bool isOuterTieOfChord = tie->isOuterTieOfChord(startOrEnd);
2239 :
2240 9131 : TieDotsPlacement dotsPlacement = tie->style().styleV(Sid::tieDotsPlacement).value<TieDotsPlacement>();
2241 4532 : bool clearAllDots = start && dotsPlacement == TieDotsPlacement::AFTER_DOTS && !isOuterTieOfChord
2242 13663 : && chord->dots() > 0 && !ChordLayout::chordHasDotsAllInvisible(chord);
2243 9131 : if (clearAllDots) {
2244 0 : double dotsOuterEdge = -DBL_MAX;
2245 0 : for (const Note* n : chord->notes()) {
2246 0 : for (const NoteDot* dot : n->dots()) {
2247 0 : dotsOuterEdge = std::max(dotsOuterEdge, dot->x() + dot->width() + n->x());
2248 : }
2249 : }
2250 0 : dotsOuterEdge += systemPos.x();
2251 0 : resultingX = dotsOuterEdge + padding;
2252 0 : tieSegment->addAdjustmentOffset(PointF(resultingX - tiePoint.x(), 0.0), startOrEnd);
2253 0 : tiePoint.setX(resultingX);
2254 0 : return;
2255 : }
2256 :
2257 9131 : if (isOuterTieOfChord) {
2258 8592 : Tie* otherTie = start ? note->tieBack() : note->tieFor();
2259 8592 : bool avoidOtherTie = otherTie && otherTie->up() == tie->up() && !otherTie->isInside();
2260 8592 : if (avoidOtherTie) {
2261 3154 : resultingX += 0.1 * spatium * (start ? 1 : -1);
2262 : }
2263 : }
2264 :
2265 9131 : const bool avoidStem = chord->stem() && chord->stem()->visible() && chord->up() == tie->up();
2266 :
2267 9131 : if (isOuterTieOfChord && !avoidStem) {
2268 7547 : tieSegment->addAdjustmentOffset(PointF(resultingX - tiePoint.x(), 0.0), startOrEnd);
2269 7547 : tiePoint.setX(resultingX);
2270 7547 : return;
2271 : }
2272 :
2273 1584 : Shape shape = isGrace ? chord->shape().translate(systemPos) : chordSeg->staffShape(chord->vStaffIdx()).translated(systemPos);
2274 1584 : bool ignoreDot = start && (isOuterTieOfChord || dotsPlacement == TieDotsPlacement::BEFORE_DOTS);
2275 1584 : bool ignoreLvSeg = tieSegment->isLaissezVibSegment();
2276 1584 : bool ignoreArpeggio = note == chord->downNote() && !tie->up();
2277 1584 : bool ignoreParen = tieSegment->isLaissezVibSegment() && start;
2278 771 : auto ignoreAccidental = [&](const Accidental* acc) {
2279 771 : bool accIsInwardOfTie = tie->up() ? acc->line() >= note->line() : acc->line() <= note->line();
2280 771 : return !start && isOuterTieOfChord && accIsInwardOfTie;
2281 1584 : };
2282 :
2283 : static const std::set<ElementType> IGNORED_TYPES = {
2284 : ElementType::HOOK,
2285 : ElementType::STEM_SLASH,
2286 : ElementType::LEDGER_LINE,
2287 : ElementType::LYRICS,
2288 : ElementType::HARMONY,
2289 : ElementType::FRET_DIAGRAM
2290 1600 : };
2291 1584 : shape.remove_if([&](ShapeElement& s) {
2292 22128 : bool remove = !s.item() || s.item() == note || muse::contains(IGNORED_TYPES, s.item()->type())
2293 6550 : || (s.item()->isNoteDot() && ignoreDot)
2294 6490 : || (s.item()->isAccidental() && ignoreAccidental(toAccidental(s.item())))
2295 6470 : || (s.item()->isLaissezVibSegment() && ignoreLvSeg)
2296 6270 : || ((s.item()->isArpeggio() || s.item()->isChordBracket()) && ignoreArpeggio)
2297 6264 : || (s.item()->isParenthesis() && ignoreParen)
2298 22128 : || !s.item()->addToSkyline();
2299 11064 : return remove;
2300 : });
2301 :
2302 1584 : const double arcSideMargin = 0.3 * spatium;
2303 1584 : const double pointsSideMargin = 0.15 * spatium;
2304 1584 : const double yBelow = tiePoint.y() - (tie->up() ? arcSideMargin : pointsSideMargin);
2305 1584 : const double yAbove = tiePoint.y() + (tie->up() ? pointsSideMargin : arcSideMargin);
2306 1584 : double pointToClear = start ? shape.rightMostEdgeAtHeight(yBelow, yAbove)
2307 791 : : shape.leftMostEdgeAtHeight(yBelow, yAbove);
2308 :
2309 1584 : pointToClear += padding;
2310 :
2311 1584 : resultingX = start ? std::max(resultingX, pointToClear) : std::min(resultingX, pointToClear);
2312 :
2313 1584 : adjustXforLedgerLines(tieSegment, start, chord, note, systemPos, padding, resultingX);
2314 :
2315 1584 : tieSegment->addAdjustmentOffset(PointF(resultingX - tiePoint.x(), 0.0), startOrEnd);
2316 1584 : tiePoint.setX(resultingX);
2317 1584 : }
2318 :
2319 1584 : void SlurTieLayout::adjustXforLedgerLines(TieSegment* tieSegment, bool start, Chord* chord, Note* note,
2320 : const PointF& chordSystemPos, double padding, double& resultingX)
2321 : {
2322 1584 : if (tieSegment->tie()->isInside() || chord->ledgerLines().empty()) {
2323 1399 : return;
2324 : }
2325 :
2326 758 : bool isOuterNote = note == chord->upNote() || note == chord->downNote();
2327 758 : if (isOuterNote) {
2328 325 : return;
2329 : }
2330 :
2331 433 : bool ledgersAbove = false;
2332 433 : bool ledgersBelow = false;
2333 2016 : for (LedgerLine* ledger : chord->ledgerLines()) {
2334 1591 : if (ledger->y() < 0.0) {
2335 456 : ledgersAbove = true;
2336 : } else {
2337 1135 : ledgersBelow = true;
2338 : }
2339 1591 : if (ledgersAbove && ledgersBelow) {
2340 8 : break;
2341 : }
2342 : }
2343 :
2344 433 : int noteLine = note->line();
2345 433 : bool isOddLine = noteLine % 2 != 0;
2346 433 : bool isAboveStaff = noteLine <= 0;
2347 433 : bool isBelowStaff = noteLine >= 2 * (note->staff()->lines(note->tick()) - 1);
2348 433 : bool isInsideStaff = !isAboveStaff && !isBelowStaff;
2349 433 : if (isOddLine || isInsideStaff || (isAboveStaff && !ledgersAbove) || (isBelowStaff && !ledgersBelow)) {
2350 248 : return;
2351 : }
2352 :
2353 185 : Shape noteShape = note->shape();
2354 185 : noteShape.remove_if([&](ShapeElement& s) {
2355 237 : return !s.item()->addToSkyline() || s.item()->isNoteDot() || (tieSegment->isLaissezVibSegment() && s.item()->isLaissezVibSegment());
2356 : });
2357 185 : noteShape.translate(note->pos() + chordSystemPos);
2358 185 : double xNoteEdge = (start ? noteShape.right() : noteShape.left()) + padding;
2359 :
2360 185 : resultingX = start ? std::max(resultingX, xNoteEdge) : std::min(resultingX, xNoteEdge);
2361 185 : }
2362 :
2363 5351 : void SlurTieLayout::adjustYforLedgerLines(TieSegment* tieSegment, SlurTiePos& sPos)
2364 : {
2365 5351 : Tie* tie = tieSegment->tie();
2366 5351 : Note* note = tieSegment->isSingleBeginType() ? tie->startNote() : tie->endNote();
2367 5351 : if (!note) {
2368 4443 : return;
2369 : }
2370 :
2371 5335 : Chord* chord = note->chord();
2372 5335 : if (chord->ledgerLines().empty()) {
2373 4427 : return;
2374 : }
2375 908 : PointF chordSystemPos = chord->pos() + chord->segment()->pos() + chord->segment()->measure()->pos();
2376 908 : PointF& tiePoint = tieSegment->isSingleBeginType() ? sPos.p1 : sPos.p2;
2377 908 : double spatium = tie->spatium();
2378 908 : int upSign = tie->up() ? -1 : 1;
2379 908 : double margin = 0.4 * spatium;
2380 :
2381 3119 : for (LedgerLine* ledger : chord->ledgerLines()) {
2382 2284 : PointF ledgerPos = ledger->pos() + chordSystemPos;
2383 2284 : double yDiff = upSign * (ledgerPos.y() - tiePoint.y());
2384 2284 : bool collision = yDiff > 0 && yDiff < margin;
2385 2284 : if (collision) {
2386 73 : sPos.p1 += PointF(0.0, -upSign * (margin - yDiff));
2387 73 : sPos.p2 += PointF(0.0, -upSign * (margin - yDiff));
2388 73 : computeBezier(tieSegment);
2389 73 : break;
2390 : }
2391 : }
2392 : }
2393 :
2394 5940 : void SlurTieLayout::adjustY(TieSegment* tieSegment)
2395 : {
2396 5940 : Staff* staff = tieSegment->score() ? tieSegment->score()->staff(tieSegment->vStaffIdx()) : nullptr;
2397 5940 : if (!staff) {
2398 2775 : return;
2399 : }
2400 :
2401 5940 : const double spatium = tieSegment->spatium();
2402 5940 : TieSegment::LayoutData* ldata = tieSegment->mutldata();
2403 :
2404 5940 : ldata->setPos(PointF());
2405 5940 : ldata->moveY(tieSegment->staffOffsetY());
2406 :
2407 5940 : Fraction tick = tieSegment->tick();
2408 :
2409 5940 : computeBezier(tieSegment);
2410 :
2411 5940 : bool up = tieSegment->tie()->up();
2412 5940 : int upSign = up ? -1 : 1;
2413 :
2414 5940 : const double staffLineDist = staff->lineDistance(tick) * spatium;
2415 5940 : const double staffLineThickness = tieSegment->style().styleAbsolute(Sid::staffLineWidth) * staff->staffMag(tick);
2416 :
2417 : // 1. Check for bad end point protrusion
2418 :
2419 5940 : const double endPointY = tieSegment->ups(Grip::START).p.y();
2420 5940 : const int closestLineToEndpoints = up ? floor(endPointY / staffLineDist) : ceil(endPointY / staffLineDist);
2421 5940 : const bool isEndInsideStaff = closestLineToEndpoints >= 0 && closestLineToEndpoints < staff->lines(tick);
2422 5940 : const bool isEndInsideLedgerLines = !isEndInsideStaff && !tieSegment->tie()->isOuterTieOfChord(Grip::START);
2423 :
2424 5940 : const double halfLineThicknessCorrection = 0.5 * staffLineThickness * upSign;
2425 5940 : const double protrusion = std::abs(endPointY - (closestLineToEndpoints * spatium - halfLineThicknessCorrection));
2426 5940 : const double badIntersectionLimit = 0.15 * spatium; // TODO: style
2427 :
2428 5940 : bool badIntersection = protrusion < badIntersectionLimit && (isEndInsideStaff || isEndInsideLedgerLines);
2429 5940 : if (badIntersection) {
2430 384 : double correctedY = closestLineToEndpoints * spatium + halfLineThicknessCorrection + badIntersectionLimit * upSign;
2431 384 : tieSegment->addAdjustmentOffset(PointF(0.0, correctedY - endPointY), Grip::START);
2432 384 : tieSegment->addAdjustmentOffset(PointF(0.0, correctedY - endPointY), Grip::END);
2433 384 : tieSegment->ups(Grip::START).p.setY(correctedY);
2434 384 : tieSegment->ups(Grip::END).p.setY(correctedY);
2435 384 : computeBezier(tieSegment);
2436 : }
2437 :
2438 : // 2. Check for bad arc protrusion
2439 :
2440 5940 : RectF tieSegmentBBox = tieSegment->ldata()->bbox();
2441 5940 : double tieLength = tieSegmentBBox.width();
2442 5940 : double tieHeight = tieSegmentBBox.height();
2443 5940 : double midThickness = tieSegment->ldata()->midThickness() * 2;
2444 5940 : double yOuterApogee = up ? tieSegmentBBox.top() : tieSegmentBBox.bottom();
2445 5940 : double yInnerApogee = yOuterApogee - midThickness * upSign;
2446 5940 : double yMidApogee = 0.5 * (yOuterApogee + yInnerApogee);
2447 :
2448 5940 : int closestLineToArc = round(yMidApogee / staffLineDist);
2449 5940 : bool isArcInsideStaff = closestLineToArc >= 0 && closestLineToArc < staff->lines(tick);
2450 5940 : if (!isArcInsideStaff) {
2451 1274 : return;
2452 : }
2453 :
2454 4666 : double outwardMargin = -upSign * (yOuterApogee - (closestLineToArc * staffLineDist - halfLineThicknessCorrection));
2455 4666 : double inwardMargin = upSign * (yInnerApogee - (closestLineToArc * staffLineDist + halfLineThicknessCorrection));
2456 4666 : const double badArcIntersectionLimit = tieLength < 3 * spatium ? 0.1 * spatium : 0.15 * spatium;
2457 :
2458 4666 : bool increaseArc = outwardMargin - 0.5 * badArcIntersectionLimit < inwardMargin;
2459 4666 : bool correctOutwards = inwardMargin < badArcIntersectionLimit && increaseArc;
2460 4666 : bool correctInwards = outwardMargin < badArcIntersectionLimit && !increaseArc;
2461 :
2462 4666 : if (!correctInwards && !correctOutwards) {
2463 1441 : return;
2464 : }
2465 :
2466 3225 : bool isSmallTie = tieLength < 2.0 * spatium && tieHeight < 0.7 * spatium;
2467 : // For ties this small, try to fit them within a staff space before changing their arc
2468 3225 : if (isSmallTie) {
2469 2354 : Tie* tie2 = tieSegment->tie();
2470 2354 : bool isInside = tie2->isInside();
2471 2354 : bool isOuterOfChord = tie2->isOuterTieOfChord(Grip::START) || tie2->isOuterTieOfChord(Grip::END);
2472 2354 : bool hasTiedSecondInside = tie2->hasTiedSecondInside();
2473 2354 : if (!isInside && !isOuterOfChord && !hasTiedSecondInside && !hasEndPointAboveNote(tieSegment)) {
2474 60 : double currentY = tieSegment->ups(Grip::START).p.y();
2475 60 : double yCorrection = -upSign * (badArcIntersectionLimit - outwardMargin);
2476 60 : tieSegment->addAdjustmentOffset(PointF(0.0, yCorrection), Grip::START);
2477 60 : tieSegment->addAdjustmentOffset(PointF(0.0, yCorrection), Grip::END);
2478 60 : tieSegment->ups(Grip::START).p.setY(currentY + yCorrection);
2479 60 : tieSegment->ups(Grip::END).p.setY(currentY + yCorrection);
2480 60 : computeBezier(tieSegment);
2481 60 : return;
2482 : } else {
2483 2294 : correctOutwards = true;
2484 : }
2485 : }
2486 :
2487 3165 : double arcCorrection = correctOutwards ? (badArcIntersectionLimit - inwardMargin) : (badArcIntersectionLimit - outwardMargin);
2488 3165 : double maxArcCorrection = 0.75 * tieHeight;
2489 3165 : double rest = arcCorrection > maxArcCorrection ? arcCorrection - maxArcCorrection : 0.0;
2490 3165 : arcCorrection = std::min(arcCorrection, maxArcCorrection);
2491 3165 : if (correctOutwards) {
2492 2837 : if (rest > 0) {
2493 1940 : rest *= upSign;
2494 1940 : double currentY = tieSegment->ups(Grip::START).p.y();
2495 1940 : tieSegment->addAdjustmentOffset(PointF(0.0, rest), Grip::START);
2496 1940 : tieSegment->addAdjustmentOffset(PointF(0.0, rest), Grip::END);
2497 1940 : tieSegment->ups(Grip::START).p.setY(currentY + rest);
2498 1940 : tieSegment->ups(Grip::END).p.setY(currentY + rest);
2499 : }
2500 2837 : computeBezier(tieSegment, PointF(0.0, -arcCorrection));
2501 328 : } else if (correctInwards) {
2502 328 : computeBezier(tieSegment, PointF(0.0, arcCorrection));
2503 : }
2504 : }
2505 :
2506 147 : bool SlurTieLayout::hasEndPointAboveNote(TieSegment* tieSegment)
2507 : {
2508 147 : Note* startNote = tieSegment->tie()->startNote();
2509 147 : Note* endNote = tieSegment->tie()->endNote();
2510 :
2511 147 : if ((tieSegment->isSingleBeginType() && !startNote) || (tieSegment->isSingleEndType() && !endNote)) {
2512 0 : return false;
2513 : }
2514 :
2515 147 : Chord* startChord = startNote->chord();
2516 147 : PointF startNotePos = startNote->pos() + startChord->pos() + startChord->segment()->pos() + startChord->measure()->pos();
2517 :
2518 147 : Chord* endChord = endNote->chord();
2519 147 : PointF endNotePos = endNote->pos() + endChord->pos() + endChord->segment()->pos() + endChord->measure()->pos();
2520 :
2521 147 : PointF tieStartPos = tieSegment->ups(Grip::START).pos();
2522 147 : PointF tieEndPos = tieSegment->ups(Grip::END).pos();
2523 :
2524 227 : return (tieStartPos.x() < startNotePos.x() + startNote->width() && !startNote->shouldHideFret())
2525 227 : || (tieEndPos.x() > endNotePos.x() && !endNote->shouldHideFret());
2526 : }
2527 :
2528 96608 : void SlurTieLayout::resolveVerticalTieCollisions(const std::vector<TieSegment*>& stackedTies)
2529 : {
2530 96608 : if (stackedTies.size() < 2) {
2531 96514 : return;
2532 : }
2533 :
2534 94 : std::list<TieSegment*> downwardTies;
2535 94 : std::list<TieSegment*> upwardTies;
2536 391 : for (TieSegment* tieSegment : stackedTies) {
2537 297 : if (!tieSegment->tie()->up()) {
2538 154 : downwardTies.push_front(tieSegment);
2539 : } else {
2540 143 : upwardTies.push_back(tieSegment);
2541 : }
2542 : }
2543 :
2544 110 : auto fixTieCollision = [](TieSegment* thisTie, TieSegment* nextTie) {
2545 110 : double spatium = thisTie->spatium();
2546 110 : bool up = thisTie->tie()->up();
2547 110 : int upSign = up ? -1 : 1;
2548 :
2549 110 : double thisTieOuterY = up ? thisTie->ldata()->bbox().top() : thisTie->ldata()->bbox().bottom();
2550 110 : double nextTieInnerY = (up ? nextTie->ldata()->bbox().top() : nextTie->ldata()->bbox().bottom())
2551 110 : - upSign * 2 * nextTie->ldata()->midThickness();
2552 110 : double clearanceMargin = 0.15 * spatium;
2553 110 : bool collision = upSign * (nextTieInnerY - thisTieOuterY) < clearanceMargin;
2554 110 : if (!collision) {
2555 110 : return;
2556 : }
2557 :
2558 0 : if (!thisTie->score()) {
2559 0 : return;
2560 : }
2561 0 : Staff* staff = thisTie->score() ? thisTie->score()->staff(thisTie->vStaffIdx()) : nullptr;
2562 0 : if (!staff) {
2563 0 : return;
2564 : }
2565 :
2566 0 : Fraction tick = thisTie->tick();
2567 0 : double yMidPoint = 0.5 * (thisTieOuterY + nextTieInnerY);
2568 0 : double halfLineDist = 0.5 * staff->lineDistance(tick) * spatium;
2569 0 : int midLine = round(yMidPoint / halfLineDist);
2570 0 : bool insideStaff = midLine >= -1 && midLine <= 2 * (staff->lines(tick) - 1) + 1;
2571 0 : if (insideStaff) {
2572 0 : yMidPoint = midLine * halfLineDist;
2573 : }
2574 :
2575 0 : double thisTieYCorrection = yMidPoint - upSign * 0.5 * clearanceMargin - thisTieOuterY;
2576 0 : double nextTieYCorrection = yMidPoint + upSign * 0.5 * clearanceMargin - nextTieInnerY;
2577 :
2578 0 : double thisShoulderOff = thisTie->adjustmentOffset(Grip::BEZIER1).y();
2579 0 : double nextShoulderOff = nextTie->adjustmentOffset(Grip::BEZIER1).y();
2580 : // Subtract it otherwise it gets summed twice
2581 0 : thisTie->addAdjustmentOffset(PointF(0.0, -thisShoulderOff), Grip::BEZIER1);
2582 0 : thisTie->addAdjustmentOffset(PointF(0.0, -thisShoulderOff), Grip::BEZIER2);
2583 0 : nextTie->addAdjustmentOffset(PointF(0.0, -nextShoulderOff), Grip::BEZIER1);
2584 0 : nextTie->addAdjustmentOffset(PointF(0.0, -nextShoulderOff), Grip::BEZIER2);
2585 :
2586 0 : computeBezier(thisTie, PointF(0.0, -upSign * (thisShoulderOff + thisTieYCorrection)));
2587 0 : computeBezier(nextTie, PointF(0.0, -upSign * (nextShoulderOff + nextTieYCorrection)));
2588 : };
2589 :
2590 94 : if (upwardTies.size() >= 2) {
2591 180 : for (auto it = upwardTies.begin(); std::next(it, 1) != upwardTies.end(); ++it) {
2592 49 : TieSegment* thisTie = *it;
2593 49 : TieSegment* nextTie = *(std::next(it, 1));
2594 49 : fixTieCollision(thisTie, nextTie);
2595 : }
2596 : }
2597 :
2598 94 : if (downwardTies.size() >= 2) {
2599 218 : for (auto it = downwardTies.begin(); std::next(it, 1) != downwardTies.end(); ++it) {
2600 61 : TieSegment* thisTie = *it;
2601 61 : TieSegment* nextTie = *(std::next(it, 1));
2602 61 : fixTieCollision(thisTie, nextTie);
2603 : }
2604 : }
2605 94 : }
2606 :
2607 1751 : void SlurTieLayout::computeUp(Slur* slur, LayoutContext& ctx)
2608 : {
2609 1751 : switch (slur->slurDirection()) {
2610 97 : case DirectionV::UP:
2611 97 : slur->setUp(true);
2612 97 : break;
2613 89 : case DirectionV::DOWN:
2614 89 : slur->setUp(false);
2615 89 : break;
2616 1565 : case DirectionV::AUTO:
2617 : {
2618 : //
2619 : // assumption:
2620 : // slurs have only chords or rests as start/end elements
2621 : //
2622 1565 : ChordRest* chordRest1 = slur->startCR();
2623 1565 : ChordRest* chordRest2 = slur->endCR();
2624 1565 : if (chordRest1 == 0 || chordRest2 == 0) {
2625 0 : slur->setUp(true);
2626 0 : break;
2627 : }
2628 1565 : Chord* chord1 = slur->startCR()->isChord() ? toChord(slur->startCR()) : 0;
2629 1565 : Chord* chord2 = slur->endCR()->isChord() ? toChord(slur->endCR()) : 0;
2630 1543 : if (chord2 && !chord2->staff()->isDrumStaff(chord2->tick())
2631 3108 : && slur->startCR()->measure()->system() != slur->endCR()->measure()->system()) {
2632 : // HACK: if the end chord is in a different system, it may have never been laid out yet.
2633 : // But we need to know its direction to decide slur direction, so need to compute it here.
2634 437 : for (Note* note : chord2->notes()) {
2635 219 : note->updateLine(); // because chord direction is based on note lines
2636 : }
2637 :
2638 218 : ChordLayout::computeUp(chord2, ctx);
2639 : }
2640 :
2641 1565 : if (chord1 && chord1->beam() && chord1->beam()->cross()) {
2642 : // TODO: stem direction is not finalized, so we cannot use it here
2643 0 : slur->setUp(true);
2644 0 : break;
2645 : }
2646 :
2647 1565 : slur->setUp(!(chordRest1->up()));
2648 :
2649 : // Check if multiple voices
2650 1565 : bool multipleVoices = false;
2651 1565 : Measure* m1 = chordRest1->measure();
2652 3222 : while (m1 && m1->tick() <= chordRest2->tick()) {
2653 1923 : if ((m1->hasVoices(chordRest1->staffIdx(), slur->tick(), slur->ticks() + chordRest2->ticks()))
2654 1923 : && chord1) {
2655 266 : multipleVoices = true;
2656 266 : break;
2657 : }
2658 1657 : m1 = m1->nextMeasure();
2659 : }
2660 1565 : if (multipleVoices) {
2661 : // slurs go on the stem side
2662 266 : if (chordRest1->voice() > 0 || chordRest2->voice() > 0) {
2663 179 : slur->setUp(false);
2664 : } else {
2665 87 : slur->setUp(true);
2666 : }
2667 1299 : } else if (chord1 && chord2 && !chord1->isGrace() && isDirectionMixture(chord1, chord2, ctx)) {
2668 : // slurs go above if there are mixed direction stems between c1 and c2
2669 : // but grace notes are exceptions
2670 161 : slur->setUp(true);
2671 1138 : } else if (chord1 && chord2 && chord1->isGrace() && chord2 != chord1->parent() && isDirectionMixture(chord1, chord2, ctx)) {
2672 0 : slur->setUp(true);
2673 : }
2674 : }
2675 1565 : break;
2676 : }
2677 1751 : }
2678 :
2679 9622 : void SlurTieLayout::computeBezier(TieSegment* tieSeg, PointF shoulderOffset)
2680 : {
2681 9622 : const PointF tieStart = tieSeg->ups(Grip::START).p + tieSeg->ups(Grip::START).off;
2682 9622 : const PointF tieEnd = tieSeg->ups(Grip::END).p + tieSeg->ups(Grip::END).off;
2683 9622 : if (!muse::RealIsEqualOrMore(tieEnd.x(), tieStart.x())) {
2684 4452 : return;
2685 : }
2686 :
2687 6286 : PointF tieEndNormalized = tieEnd - tieStart; // normalize to zero
2688 6286 : if (muse::RealIsNull(tieEndNormalized.x())) {
2689 1116 : return;
2690 : }
2691 :
2692 5170 : const double tieAngle = atan(tieEndNormalized.y() / tieEndNormalized.x()); // angle required from tie start to tie end--zero if horizontal
2693 5170 : Transform t;
2694 5170 : t.rotateRadians(-tieAngle); // rotate so that we are working with horizontal ties regardless of endpoint height difference
2695 5170 : tieEndNormalized = t.map(tieEndNormalized); // apply that rotation
2696 5170 : shoulderOffset = t.map(shoulderOffset); // also apply to shoulderOffset
2697 :
2698 5170 : const double _spatium = tieSeg->spatium();
2699 5170 : double tieLengthInSp = tieEndNormalized.x() / _spatium;
2700 :
2701 5170 : const double minShoulderHeight = tieSeg->minShoulderHeight();
2702 5170 : const double maxShoulderHeight = tieSeg->maxShoulderHeight();
2703 5170 : double shoulderH = minShoulderHeight + _spatium * 0.3 * sqrt(std::abs(tieLengthInSp - 1));
2704 5170 : shoulderH = std::clamp(shoulderH, minShoulderHeight, maxShoulderHeight);
2705 :
2706 5170 : shoulderH -= shoulderOffset.y();
2707 :
2708 5170 : PointF shoulderAdjustOffset = tieSeg->tie()->up() ? PointF(0.0, shoulderOffset.y()) : PointF(0.0, -shoulderOffset.y());
2709 5170 : tieSeg->addAdjustmentOffset(shoulderAdjustOffset, Grip::BEZIER1);
2710 5170 : tieSeg->addAdjustmentOffset(shoulderAdjustOffset, Grip::BEZIER2);
2711 :
2712 5170 : if (!tieSeg->tie()->up()) {
2713 1481 : shoulderH = -shoulderH;
2714 : }
2715 :
2716 5170 : double shoulderW = 0.6; // TODO: style
2717 :
2718 5170 : const double tieWidth = tieEndNormalized.x();
2719 5170 : const double bezier1X = (tieWidth - tieWidth * shoulderW) * .5 + shoulderOffset.x();
2720 5170 : const double bezier2X = bezier1X + tieWidth * shoulderW + shoulderOffset.x();
2721 :
2722 5170 : const PointF tieDrag = PointF(tieWidth * .5, 0.0);
2723 :
2724 5170 : const PointF bezier1(bezier1X, -shoulderH);
2725 5170 : const PointF bezier2(bezier2X, -shoulderH);
2726 :
2727 5170 : computeMidThickness(tieSeg, tieLengthInSp);
2728 :
2729 5170 : PointF tieThickness(0.0, 0.0);
2730 5170 : if (tieSeg->tie()->styleType() == SlurStyleType::Solid) {
2731 5142 : tieThickness.setY(tieSeg->ldata()->midThickness());
2732 : }
2733 :
2734 5170 : const PointF bezier1Offset = t.map(tieSeg->ups(Grip::BEZIER1).off);
2735 5170 : const PointF bezier2Offset = t.map(tieSeg->ups(Grip::BEZIER2).off);
2736 :
2737 : //-----------------------------------calculate p6
2738 5170 : const PointF bezier1Final = bezier1 + bezier1Offset;
2739 5170 : const PointF bezier2Final = bezier2 + bezier2Offset;
2740 :
2741 5170 : const PointF tieShoulder = 0.5 * (bezier1Final + bezier2Final);
2742 : //-----------------------------------
2743 :
2744 5170 : PainterPath path = PainterPath();
2745 5170 : path.moveTo(PointF());
2746 5170 : path.cubicTo(bezier1 + bezier1Offset - tieThickness, bezier2 + bezier2Offset - tieThickness, tieEndNormalized);
2747 5170 : if (tieSeg->tie()->styleType() == SlurStyleType::Solid) {
2748 5142 : path.cubicTo(bezier2 + bezier2Offset + tieThickness, bezier1 + bezier1Offset + tieThickness, PointF());
2749 : }
2750 :
2751 : // translate back
2752 5170 : t.reset();
2753 5170 : t.translate(tieStart.x(), tieStart.y());
2754 5170 : t.rotateRadians(tieAngle);
2755 5170 : path = t.map(path);
2756 5170 : tieSeg->mutldata()->path.set_value(path);
2757 :
2758 5170 : tieSeg->ups(Grip::BEZIER1).p = t.map(bezier1);
2759 5170 : tieSeg->ups(Grip::BEZIER2).p = t.map(bezier2);
2760 5170 : tieSeg->ups(Grip::END).p = t.map(tieEndNormalized) - tieSeg->ups(Grip::END).off;
2761 5170 : tieSeg->ups(Grip::DRAG).p = t.map(tieDrag);
2762 5170 : tieSeg->ups(Grip::SHOULDER).p = t.map(tieShoulder);
2763 :
2764 5170 : fillShape(tieSeg, tieLengthInSp);
2765 5170 : }
2766 :
2767 953 : void SlurTieLayout::computeBezier(SlurSegment* slurSeg, PointF shoulderOffset)
2768 : {
2769 : /* ************************************************
2770 : * LEGEND: pp1 = start point
2771 : * pp2 = end point
2772 : * p2 = end point (in slur coordinates)
2773 : * p3 = first bezier point (in slur coord.)
2774 : * p4 = second bezier point (in slur coord.)
2775 : * p5 = whole slur drag point (in slur coord.)
2776 : * p6 = shoulder drag point (in slur coord.)
2777 : * REMEMBER! ups().pos() = ups().p + ups().off
2778 : * ***********************************************/
2779 : // Avoid bad staff line intersections
2780 953 : if (slurSeg->autoplace()) {
2781 947 : adjustEndPoints(slurSeg);
2782 : }
2783 : // If end point adjustment is locked, restore the endpoints to
2784 : // where they were before
2785 953 : if (slurSeg->isEndPointsEdited()) {
2786 4 : slurSeg->ups(Grip::START).off += slurSeg->endPointOff1();
2787 4 : slurSeg->ups(Grip::END).off += slurSeg->endPointOff2();
2788 : }
2789 : // Get start and end points (have been calculated before)
2790 953 : PointF pp1 = slurSeg->ups(Grip::START).p + slurSeg->ups(Grip::START).off;
2791 953 : PointF pp2 = slurSeg->ups(Grip::END).p + slurSeg->ups(Grip::END).off;
2792 : // Keep track of the original value before it gets changed
2793 953 : PointF oldp1 = pp1;
2794 953 : PointF oldp2 = pp2;
2795 :
2796 : // Set up coordinate transforms
2797 : // CAUTION: transform operations are applies in reverse order to how
2798 : // they are added to the transformation.
2799 953 : double slurAngle = atan((pp2.y() - pp1.y()) / (pp2.x() - pp1.x()));
2800 953 : Transform rotate;
2801 953 : rotate.rotateRadians(-slurAngle);
2802 953 : Transform toSlurCoordinates;
2803 953 : toSlurCoordinates.rotateRadians(-slurAngle);
2804 953 : toSlurCoordinates.translate(-pp1.x(), -pp1.y());
2805 953 : Transform toSystemCoordinates = toSlurCoordinates.inverted();
2806 : // Transform p2 and shoulder offset
2807 953 : PointF p2 = toSlurCoordinates.map(pp2);
2808 953 : shoulderOffset = rotate.map(shoulderOffset);
2809 :
2810 : // COMPUTE DEFAULT SLUR SHAPE
2811 : // Compute default shoulder height and width
2812 953 : double _spatium = slurSeg->spatium();
2813 : double shoulderW; // expressed as fraction of slur-length
2814 953 : double d = p2.x() / _spatium;
2815 :
2816 953 : if (d < 0) {
2817 : //! NOTE A negative d means that end point is before the start point.
2818 : //! This only exists as a temporary state when horizontal spacing hasn't yet been computed,
2819 : //! and it makes no sense for any of the following calculations
2820 0 : return;
2821 : }
2822 :
2823 953 : if (d < 2) {
2824 28 : shoulderW = 0.60;
2825 925 : } else if (d < 10) {
2826 475 : shoulderW = 0.5;
2827 450 : } else if (d < 18) {
2828 281 : shoulderW = 0.6;
2829 : } else {
2830 169 : shoulderW = 0.7;
2831 : }
2832 :
2833 953 : double shoulderH = computeShoulderHeight(slurSeg, d, shoulderOffset);
2834 :
2835 953 : double c = p2.x();
2836 953 : double c1 = (c - c * shoulderW) * .5 + shoulderOffset.x();
2837 953 : double c2 = c1 + c * shoulderW + shoulderOffset.x();
2838 953 : PointF p3(c1, -shoulderH);
2839 953 : PointF p4(c2, -shoulderH);
2840 : // Set Bezier points default position
2841 953 : slurSeg->ups(Grip::BEZIER1).p = toSystemCoordinates.map(p3);
2842 953 : slurSeg->ups(Grip::BEZIER2).p = toSystemCoordinates.map(p4);
2843 : // Add offsets
2844 953 : p3 += shoulderOffset + rotate.map(slurSeg->ups(Grip::BEZIER1).off);
2845 953 : p4 += shoulderOffset + rotate.map(slurSeg->ups(Grip::BEZIER2).off);
2846 953 : slurSeg->ups(Grip::BEZIER1).off += rotate.inverted().map(shoulderOffset);
2847 953 : slurSeg->ups(Grip::BEZIER2).off += rotate.inverted().map(shoulderOffset);
2848 :
2849 : // ADAPT SLUR SHAPE AND ENDPOINT POSITION
2850 : // to clear collisions with underlying items
2851 953 : if (slurSeg->autoplace() && !slurSeg->isTappingHalfSlurSegment()) {
2852 915 : avoidCollisions(slurSeg, pp1, p2, p3, p4, toSystemCoordinates, slurAngle);
2853 : }
2854 :
2855 : // Re-check end points for bad staff line collisions
2856 953 : slurSeg->ups(Grip::START).p = pp1 - slurSeg->ups(Grip::START).off;
2857 953 : slurSeg->ups(Grip::END).p = toSystemCoordinates.map(p2) - slurSeg->ups(Grip::END).off;
2858 953 : adjustEndPoints(slurSeg);
2859 953 : PointF newpp1 = slurSeg->ups(Grip::START).p + slurSeg->ups(Grip::START).off;
2860 953 : PointF difference = rotate.map(newpp1 - pp1);
2861 953 : pp1 = newpp1;
2862 953 : pp2 = slurSeg->ups(Grip::END).p + slurSeg->ups(Grip::END).off;
2863 953 : p3 -= difference;
2864 953 : p4 -= difference;
2865 : // Keep track of how much the end points position has changed
2866 953 : if (!slurSeg->isEndPointsEdited()) {
2867 949 : slurSeg->setEndPointOff1(pp1 - oldp1);
2868 949 : slurSeg->setEndPointOff2(pp2 - oldp2);
2869 : } else {
2870 4 : slurSeg->setEndPointOff1(PointF());
2871 4 : slurSeg->setEndPointOff2(PointF());
2872 : }
2873 : // Recompute the transformation because pp1 and pp1 may have changed
2874 953 : toSlurCoordinates.reset();
2875 953 : toSlurCoordinates.rotateRadians(-slurAngle);
2876 953 : toSlurCoordinates.translate(-pp1.x(), -pp1.y());
2877 953 : toSystemCoordinates = toSlurCoordinates.inverted();
2878 953 : p2 = toSlurCoordinates.map(pp2);
2879 :
2880 : // Calculate p5 and p6
2881 953 : PointF p5 = 0.5 * p2; // mid-point between pp1 and p2
2882 953 : PointF p6 = 0.5 * (p3 + p4); // mid-point between p3 and p4
2883 :
2884 : // Update all slur points after collision avoidance
2885 953 : slurSeg->ups(Grip::BEZIER1).p = toSystemCoordinates.map(p3) - slurSeg->ups(Grip::BEZIER1).off;
2886 953 : slurSeg->ups(Grip::BEZIER2).p = toSystemCoordinates.map(p4) - slurSeg->ups(Grip::BEZIER2).off;
2887 953 : slurSeg->ups(Grip::DRAG).p = toSystemCoordinates.map(p5);
2888 953 : slurSeg->ups(Grip::SHOULDER).p = toSystemCoordinates.map(p6);
2889 :
2890 : // Set slur thickness
2891 953 : computeMidThickness(slurSeg, p2.x() / slurSeg->spatium());
2892 :
2893 953 : PointF thick(0.0, 0.0);
2894 953 : if (slurSeg->slur()->styleType() == SlurStyleType::Solid) {
2895 924 : thick.setY(slurSeg->ldata()->midThickness());
2896 : }
2897 :
2898 : // Set path
2899 953 : PainterPath path = PainterPath();
2900 953 : path.moveTo(PointF());
2901 953 : path.cubicTo(p3 - thick, p4 - thick, p2);
2902 953 : if (slurSeg->slur()->styleType() == SlurStyleType::Solid) {
2903 924 : path.cubicTo(p4 + thick, p3 + thick, PointF());
2904 : }
2905 :
2906 953 : path = toSystemCoordinates.map(path);
2907 953 : slurSeg->mutldata()->path.set_value(path);
2908 :
2909 953 : fillShape(slurSeg, p2.x() / slurSeg->spatium());
2910 953 : }
2911 :
2912 953 : double SlurTieLayout::computeShoulderHeight(SlurSegment* slurSeg, double slurLengthInSp, PointF shoulderOffset)
2913 : {
2914 953 : double spatium = slurSeg->spatium();
2915 :
2916 953 : if (slurSeg->isTappingHalfSlurSegment()) {
2917 32 : double shoulderH = (slurSeg->staffType()->isTabStaff() ? 1.0 : 0.6) * spatium;
2918 32 : return slurSeg->slur()->up() ? shoulderH : -shoulderH;
2919 : }
2920 :
2921 921 : double shoulderH = sqrt(slurLengthInSp / 4) * spatium;
2922 :
2923 : static constexpr double shoulderReduction = 0.75;
2924 921 : if (isOverBeams(slurSeg->slur())) {
2925 14 : shoulderH *= shoulderReduction;
2926 : }
2927 :
2928 921 : shoulderH -= shoulderOffset.y();
2929 921 : if (!slurSeg->slur()->up()) {
2930 517 : shoulderH = -shoulderH;
2931 : }
2932 :
2933 921 : return shoulderH;
2934 : }
2935 :
2936 0 : double SlurTieLayout::defaultStemLengthStart(TremoloTwoChord* tremolo)
2937 : {
2938 0 : return TremoloLayout::extendedStemLenWithTwoNoteTremolo(tremolo,
2939 : tremolo->chord1()->defaultStemLength(),
2940 0 : tremolo->chord2()->defaultStemLength()).first;
2941 : }
2942 :
2943 0 : double SlurTieLayout::defaultStemLengthEnd(TremoloTwoChord* tremolo)
2944 : {
2945 0 : return TremoloLayout::extendedStemLenWithTwoNoteTremolo(tremolo,
2946 : tremolo->chord1()->defaultStemLength(),
2947 0 : tremolo->chord2()->defaultStemLength()).second;
2948 : }
2949 :
2950 1257 : bool SlurTieLayout::isDirectionMixture(const Chord* c1, const Chord* c2, LayoutContext& ctx)
2951 : {
2952 1257 : if (c1->track() != c2->track()) {
2953 11 : return false;
2954 : }
2955 1246 : const bool up = c1->up();
2956 1246 : if (c2->isGrace() && c2->up() != up) {
2957 6 : return true;
2958 : }
2959 1240 : if (c1->isGraceBefore() && c2->isGraceAfter() && c1->ownershipParent() == c2->ownershipParent()) {
2960 2 : const Chord* mainChord = toChord(c1->ownershipParent());
2961 2 : if (mainChord->stem() && mainChord->up() != up) {
2962 0 : return true;
2963 : }
2964 : }
2965 1240 : const track_idx_t track = c1->track();
2966 4814 : for (const Segment* seg = c1->segment(); seg && seg->tick() <= c2->tick(); seg = seg->next1(SegmentType::ChordRest)) {
2967 3774 : if ((c1->isGrace() || c2->isGraceBefore()) && seg->tick() == c2->tick()) {
2968 : // if slur ends at a grace-note-before, we don't need to look at the main note
2969 45 : return false;
2970 : }
2971 3729 : EngravingItem* e = seg->element(track);
2972 3729 : if (!e || !e->isChord()) {
2973 192 : continue;
2974 : }
2975 3537 : Chord* c = toChord(e);
2976 3537 : const Measure* m = c->measure();
2977 3537 : const System* c1Sys = c1->measure()->system();
2978 3537 : const System* cSys = m->system();
2979 3537 : if (!c->staff()->isDrumStaff(c->tick()) && c1Sys && cSys && c1Sys != cSys) {
2980 : // This chord is on a different system and may not have been laid out yet
2981 16 : for (Note* note : c->notes()) {
2982 8 : note->updateLine(); // because chord direction is based on note lines
2983 : }
2984 8 : ChordLayout::computeUp(c, ctx);
2985 : }
2986 :
2987 3537 : if (c->up() != up) {
2988 155 : return true;
2989 : }
2990 : }
2991 1040 : return false;
2992 : }
2993 :
2994 929 : bool SlurTieLayout::shouldHideSlurSegment(SlurSegment* item)
2995 : {
2996 929 : if (item->isHammerOnPullOffSegment()) {
2997 61 : return false;
2998 : }
2999 :
3000 868 : if (item->configuration()->specificSlursLayoutWorkaround()) {
3001 0 : Slur* slur = item->slur();
3002 0 : if (slur->connectedElement() == Slur::ConnectedElement::GLISSANDO) {
3003 0 : return false;
3004 : }
3005 : }
3006 :
3007 868 : const StaffType* stType = item->staffType();
3008 868 : if (stType && stType->isHiddenElementOnTab(Sid::slurShowTabCommon, Sid::slurShowTabSimple)) {
3009 0 : return true;
3010 : }
3011 :
3012 868 : return false;
3013 : }
3014 :
3015 4937 : void SlurTieLayout::addLineAttachPoints(TieSegment* segment)
3016 : {
3017 : // Add tie attach point to start and end note of segment
3018 4937 : Tie* tie = segment->tie();
3019 4937 : Note* startNote = tie->startNote();
3020 4937 : Note* endNote = tie->endNote();
3021 :
3022 4937 : const bool singleOrBegin = segment->spannerSegmentType() == SpannerSegmentType::SINGLE
3023 4937 : || segment->spannerSegmentType() == SpannerSegmentType::BEGIN;
3024 :
3025 4937 : const bool singleOrEnd = segment->spannerSegmentType() == SpannerSegmentType::SINGLE
3026 4937 : || segment->spannerSegmentType() == SpannerSegmentType::END;
3027 :
3028 4937 : if (startNote && singleOrBegin) {
3029 4348 : startNote->addStartLineAttachPoint(segment->ups(Grip::START).pos(), tie);
3030 : }
3031 4937 : if (endNote && singleOrEnd) {
3032 4369 : endNote->addEndLineAttachPoint(segment->ups(Grip::END).pos(), tie);
3033 : }
3034 4937 : }
3035 :
3036 340 : void SlurTieLayout::addLineAttachPoints(PartialTieSegment* segment)
3037 : {
3038 : // Add tie attach point to parent note
3039 340 : PartialTie* tie = segment->partialTie();
3040 340 : Note* note = tie ? tie->note() : nullptr;
3041 340 : if (!note) {
3042 0 : return;
3043 : }
3044 :
3045 340 : const bool isOutgoing = tie->isOutgoing();
3046 :
3047 340 : if (isOutgoing) {
3048 110 : note->addStartLineAttachPoint(segment->ups(Grip::START).pos(), tie);
3049 : } else {
3050 230 : note->addEndLineAttachPoint(segment->ups(Grip::END).pos(), tie);
3051 : }
3052 : }
3053 :
3054 4799 : void SlurTieLayout::calculateDirection(Tie* item)
3055 : {
3056 4799 : if (!item->startNote() && !item->endNote()) {
3057 0 : return;
3058 : }
3059 4799 : const bool tieHasBothNotes = item->startNote() && item->endNote();
3060 :
3061 4799 : const Note* primaryNote = item->startNote() ? item->startNote() : item->endNote();
3062 4799 : const Chord* primaryChord = primaryNote->chord();
3063 4799 : const Measure* primaryMeasure = primaryChord->measure();
3064 :
3065 4799 : const Note* secondaryNote = tieHasBothNotes ? item->endNote() : nullptr;
3066 4799 : const Chord* secondaryChord = secondaryNote ? secondaryNote->chord() : nullptr;
3067 4799 : const Measure* secondaryMeasure = secondaryChord ? secondaryChord->measure() : nullptr;
3068 :
3069 4799 : if (item->slurDirection() == DirectionV::AUTO) {
3070 4664 : std::vector<Note*> notes = primaryChord->notes();
3071 4664 : size_t n = notes.size();
3072 4664 : StaffType* st = item->staff()->staffType(primaryNote ? primaryNote->tick() : Fraction(0, 1));
3073 4664 : bool simpleException = st && st->isSimpleTabStaff();
3074 : // if there are multiple voices, the tie direction goes on stem side
3075 4664 : if (primaryMeasure->hasVoices(primaryChord->staffIdx(), primaryChord->tick(), primaryChord->actualTicks())) {
3076 322 : item->setUp(simpleException ? isUpVoice(primaryChord->voice()) : primaryChord->up());
3077 4342 : } else if (tieHasBothNotes && secondaryMeasure->hasVoices(secondaryChord->staffIdx(), secondaryChord->tick(),
3078 : secondaryChord->actualTicks())) {
3079 15 : item->setUp(simpleException ? isUpVoice(secondaryChord->voice()) : secondaryChord->up());
3080 4327 : } else if (n == 1) {
3081 : //
3082 : // single note
3083 : //
3084 3716 : if (tieHasBothNotes && primaryChord->up() != secondaryChord->up()) {
3085 : // if stem direction is mixed, always up
3086 165 : item->setUp(true);
3087 : } else {
3088 3551 : item->setUp(!primaryChord->up());
3089 : }
3090 : } else {
3091 : //
3092 : // chords
3093 : //
3094 : // first, find pivot point in chord (below which all ties curve down and above which all ties curve up)
3095 611 : Note* pivotPoint = nullptr;
3096 611 : bool multiplePivots = false;
3097 2340 : for (size_t i = 0; i < n - 1; ++i) {
3098 1729 : if (!notes[i]->tieFor()) {
3099 174 : continue; // don't include notes that don't have ties
3100 : }
3101 5066 : for (size_t j = i + 1; j < n; ++j) {
3102 3511 : if (!notes[j]->tieFor()) {
3103 148 : continue;
3104 : }
3105 3363 : int noteDiff = compareNotesPos(notes[i], notes[j]);
3106 3363 : if (!multiplePivots && std::abs(noteDiff) <= 1) {
3107 : // TODO: Fix unison ties somehow--if noteDiff == 0 then we need to determine which of the unison is 'lower'
3108 180 : if (pivotPoint) {
3109 18 : multiplePivots = true;
3110 18 : pivotPoint = nullptr;
3111 : } else {
3112 162 : pivotPoint = noteDiff < 0 ? notes[i] : notes[j];
3113 : }
3114 : }
3115 : }
3116 : }
3117 611 : if (!pivotPoint) {
3118 : // if the pivot point was not found (either there are no unisons/seconds or there are more than one),
3119 : // determine if this note is in the lower or upper half of this chord
3120 467 : int notesAbove = 0, tiesAbove = 0;
3121 467 : int notesBelow = 0, tiesBelow = 0;
3122 467 : int unisonTies = 0;
3123 2019 : for (size_t i = 0; i < n; ++i) {
3124 1552 : if (notes[i] == primaryNote) {
3125 : // skip counting if this note is the current note or if this note doesn't have a tie
3126 467 : continue;
3127 : }
3128 1085 : int noteDiff = compareNotesPos(primaryNote, notes[i]);
3129 1085 : if (noteDiff == 0) { // unison
3130 0 : if (notes[i]->tieFor()) {
3131 0 : unisonTies++;
3132 : }
3133 : }
3134 1085 : if (noteDiff < 0) { // the note is above startNote
3135 523 : notesAbove++;
3136 523 : if (notes[i]->tieFor()) {
3137 447 : tiesAbove++;
3138 : }
3139 : }
3140 1085 : if (noteDiff > 0) { // the note is below startNote
3141 562 : notesBelow++;
3142 562 : if (notes[i]->tieFor()) {
3143 432 : tiesBelow++;
3144 : }
3145 : }
3146 : }
3147 :
3148 467 : if (tiesAbove == 0 && tiesBelow == 0 && unisonTies == 0) {
3149 : // this is the only tie in the chord.
3150 64 : if (notesAbove == notesBelow) {
3151 4 : item->setUp(!primaryChord->up());
3152 : } else {
3153 60 : item->setUp(notesAbove < notesBelow);
3154 : }
3155 403 : } else if (tiesAbove == tiesBelow) {
3156 : // this note is dead center, so its tie should go counter to the stem direction
3157 24 : item->setUp(!primaryChord->up());
3158 : } else {
3159 379 : item->setUp(tiesAbove < tiesBelow);
3160 : }
3161 144 : } else if (pivotPoint == primaryNote) {
3162 : // the current note is the lower of the only second or unison in the chord; tie goes down.
3163 28 : item->setUp(false);
3164 : } else {
3165 : // if lower than the pivot, tie goes down, otherwise up
3166 116 : int noteDiff = compareNotesPos(primaryNote, pivotPoint);
3167 116 : item->setUp(noteDiff >= 0);
3168 : }
3169 : }
3170 4664 : } else {
3171 135 : item->setUp(item->slurDirection() == DirectionV::UP ? true : false);
3172 : }
3173 : }
3174 :
3175 4762 : void SlurTieLayout::calculateIsInside(Tie* item)
3176 : {
3177 4762 : if (item->tiePlacement() != TiePlacement::AUTO) {
3178 0 : item->setIsInside(item->tiePlacement() == TiePlacement::INSIDE);
3179 0 : return;
3180 : }
3181 :
3182 4762 : const Note* startN = item->startNote();
3183 4762 : const Chord* startChord = startN ? startN->chord() : nullptr;
3184 4762 : const Note* endN = item->endNote();
3185 4762 : const Chord* endChord = endN ? endN->chord() : nullptr;
3186 :
3187 4762 : if (!startChord && !endChord) {
3188 0 : item->setIsInside(false);
3189 0 : return;
3190 : }
3191 :
3192 4762 : const bool startIsSingleNote = startChord ? startChord->notes().size() <= 1 : false;
3193 4762 : const bool endIsSingleNote = endChord ? endChord->notes().size() <= 1 : false;
3194 :
3195 4762 : const bool shouldPlaceInside = startChord && endChord ? startIsSingleNote && endIsSingleNote : startIsSingleNote || endIsSingleNote;
3196 :
3197 4762 : if (shouldPlaceInside) {
3198 4033 : item->setIsInside(item->style().styleV(Sid::tiePlacementSingleNote).value<TiePlacement>() == TiePlacement::INSIDE);
3199 : } else {
3200 729 : item->setIsInside(item->style().styleV(Sid::tiePlacementChord).value<TiePlacement>() == TiePlacement::INSIDE);
3201 : }
3202 : }
3203 :
3204 929 : void SlurTieLayout::layoutSegment(SlurSegment* item, const PointF& p1, const PointF& p2)
3205 : {
3206 929 : SlurSegment::LayoutData* ldata = item->mutldata();
3207 929 : if (shouldHideSlurSegment(item)) {
3208 0 : ldata->setIsSkipDraw(true);
3209 0 : return;
3210 : }
3211 :
3212 929 : ldata->setIsSkipDraw(false);
3213 :
3214 929 : ldata->setPos(PointF());
3215 929 : item->ups(Grip::START).p = p1;
3216 929 : item->ups(Grip::END).p = p2;
3217 929 : item->setExtraHeight(0.0);
3218 :
3219 : //Adjust Y pos to staff type yOffset before other calculations
3220 929 : ldata->moveY(item->staffOffsetY());
3221 :
3222 929 : computeBezier(item);
3223 : }
3224 :
3225 6123 : void SlurTieLayout::computeMidThickness(SlurTieSegment* slurTieSeg, double slurTieLengthInSp)
3226 : {
3227 6123 : Staff* staff = slurTieSeg->score() ? slurTieSeg->score()->staff(slurTieSeg->vStaffIdx()) : nullptr;
3228 6123 : const double mag = staff ? staff->staffMag(slurTieSeg->slurTie()->tick()) : 1.0;
3229 6123 : const double scalingFactor = slurTieSeg->slurTie()->scalingFactor();
3230 :
3231 6123 : if (slurTieSeg->slurTie()->styleType() != SlurStyleType::Solid) {
3232 57 : slurTieSeg->mutldata()->midThickness.set_value(0.5 * slurTieSeg->dottedWidth() * mag * scalingFactor);
3233 57 : return;
3234 : }
3235 :
3236 6066 : const double endWidth = slurTieSeg->endWidth();
3237 6066 : const double midWidth = slurTieSeg->midWidth();
3238 6066 : const double minTieLength = mag * slurTieSeg->style().styleS(Sid::minTieLength).val();
3239 6066 : const double shortTieLimit = mag * 4.0;
3240 6066 : const double minTieThickness = mag * (0.15 * slurTieSeg->spatium() - endWidth);
3241 6066 : const double normalThickness = mag * (midWidth - endWidth);
3242 :
3243 6066 : bool invalid = muse::RealIsEqualOrMore(minTieLength, shortTieLimit);
3244 :
3245 : double finalThickness;
3246 6066 : if (slurTieLengthInSp > shortTieLimit || invalid) {
3247 3074 : finalThickness = normalThickness;
3248 : } else {
3249 2992 : const double A = 1 / (shortTieLimit - minTieLength);
3250 2992 : const double B = normalThickness - minTieThickness;
3251 2992 : const double C = shortTieLimit * minTieThickness - minTieLength * normalThickness;
3252 2992 : finalThickness = A * (B * slurTieLengthInSp + C);
3253 : }
3254 :
3255 6066 : finalThickness = std::min(finalThickness, normalThickness * scalingFactor);
3256 :
3257 6066 : slurTieSeg->mutldata()->midThickness.set_value(finalThickness);
3258 : }
3259 :
3260 6123 : void SlurTieLayout::fillShape(SlurTieSegment* slurTieSeg, double slurTieLengthInSp)
3261 : {
3262 6123 : Shape shape(Shape::Type::Composite);
3263 6123 : PointF startPoint = slurTieSeg->ups(Grip::START).pos();
3264 :
3265 6123 : double midThickness = 2 * slurTieSeg->ldata()->midThickness();
3266 6123 : int nbShapes = round(5.0 * slurTieLengthInSp);
3267 6123 : nbShapes = std::max(nbShapes, 20);
3268 6123 : nbShapes = std::min(nbShapes, 50);
3269 12246 : const CubicBezier b(startPoint, slurTieSeg->ups(Grip::BEZIER1).pos(), slurTieSeg->ups(Grip::BEZIER2).pos(),
3270 18369 : slurTieSeg->ups(Grip::END).pos());
3271 199419 : for (int i = 1; i <= nbShapes; i++) {
3272 193296 : double percent = pow(sin(0.5 * M_PI * (double(i) / double(nbShapes))), 2);
3273 193296 : const PointF point = b.pointAtPercent(percent);
3274 193296 : RectF re = RectF(startPoint, point).normalized();
3275 193296 : double approxThicknessAtPercent = midThickness;
3276 193296 : if (slurTieSeg->slurTie()->styleType() == SlurStyleType::Solid) {
3277 190839 : approxThicknessAtPercent *= (1 - 2 * std::abs(0.5 - percent));
3278 : }
3279 193296 : if (re.height() < approxThicknessAtPercent) {
3280 88756 : double adjust = (approxThicknessAtPercent - re.height()) * .5;
3281 88756 : re.adjust(0.0, -adjust, 0.0, adjust);
3282 : }
3283 193296 : shape.add(re, slurTieSeg);
3284 193296 : startPoint = point;
3285 : }
3286 :
3287 6123 : slurTieSeg->mutldata()->setShape(shape);
3288 6123 : }
|