Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2023 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include <cfloat>
23 :
24 : #include "horizontalspacing.h"
25 : #include "chordbracketlayout.h"
26 : #include "parenthesislayout.h"
27 :
28 : #include "dom/barline.h"
29 : #include "dom/beam.h"
30 : #include "dom/chord.h"
31 : #include "dom/chordbracket.h"
32 : #include "dom/engravingitem.h"
33 : #include "dom/glissando.h"
34 : #include "dom/lyrics.h"
35 : #include "dom/measure.h"
36 : #include "dom/note.h"
37 : #include "dom/rest.h"
38 : #include "dom/score.h"
39 : #include "dom/stemslash.h"
40 : #include "dom/staff.h"
41 : #include "dom/stem.h"
42 : #include "dom/system.h"
43 : #include "dom/tie.h"
44 : #include "dom/timesig.h"
45 :
46 : using namespace mu::engraving;
47 : using namespace mu::engraving::rendering::score;
48 :
49 7186 : double HorizontalSpacing::computeSpacingForFullSystem(System* system, double stretchReduction, double squeezeFactor,
50 : bool overrideMinMeasureWidth)
51 : {
52 7201 : TRACEFUNC;
53 :
54 7186 : if (system->score()->allStavesInvisible()) {
55 0 : return 0.0;
56 : }
57 :
58 7186 : HorizontalSpacingContext ctx;
59 7186 : ctx.system = system;
60 7186 : ctx.spatium = system->spatium();
61 :
62 7186 : ctx.stretchReduction = stretchReduction;
63 7186 : ctx.squeezeFactor = squeezeFactor;
64 7186 : ctx.spacingDensity = system->style().styleD(Sid::spacingDensity);
65 7186 : ctx.overrideMinMeasureWidth = overrideMinMeasureWidth;
66 :
67 7186 : ctx.xCur = system->leftMargin();
68 7186 : ctx.xLeftBarrier = system->leftMargin();
69 7186 : ctx.systemIsFull = true;
70 :
71 7186 : std::vector<Measure*> measureGroup;
72 7186 : measureGroup.reserve(system->measures().size());
73 :
74 37628 : for (MeasureBase* mb : system->measures()) {
75 30442 : if (mb->isHBox()) {
76 147 : spaceMeasureGroup(measureGroup, ctx);
77 147 : measureGroup.clear();
78 :
79 147 : mb->mutldata()->setPosX(ctx.xCur);
80 147 : mb->computeMinWidth();
81 147 : ctx.xCur += mb->width();
82 : } else {
83 30295 : measureGroup.push_back(toMeasure(mb));
84 : }
85 : }
86 :
87 7186 : spaceMeasureGroup(measureGroup, ctx);
88 :
89 7186 : return ctx.xCur;
90 7186 : }
91 :
92 54783 : double HorizontalSpacing::updateSpacingForLastAddedMeasure(System* system, bool startOfContinuousLayoutRegion)
93 : {
94 54798 : TRACEFUNC;
95 :
96 54783 : if (system->score()->allStavesInvisible()) {
97 189 : return 0.0;
98 : }
99 :
100 54594 : HorizontalSpacingContext ctx;
101 54594 : ctx.system = system;
102 54594 : ctx.spatium = system->spatium();
103 54594 : ctx.xLeftBarrier = system->leftMargin();
104 54594 : ctx.spacingDensity = system->style().styleD(Sid::spacingDensity);
105 :
106 54594 : size_t measureCount = system->measures().size();
107 54594 : IF_ASSERT_FAILED(measureCount > 0) {
108 0 : return 0.0;
109 : }
110 :
111 54594 : MeasureBase* secondToLast = measureCount >= 2 ? system->measures()[measureCount - 2] : nullptr;
112 54594 : MeasureBase* last = system->measures()[measureCount - 1];
113 :
114 54594 : if (secondToLast) {
115 47491 : ctx.xCur = secondToLast->x();
116 47491 : if (secondToLast->isHBox() || last->isHBox() || startOfContinuousLayoutRegion) {
117 249 : ctx.xCur += secondToLast->width();
118 : }
119 : } else {
120 7103 : ctx.xCur = system->leftMargin();
121 : }
122 :
123 54594 : if (last->isHBox()) {
124 211 : last->mutldata()->setPosX(ctx.xCur);
125 211 : last->computeMinWidth();
126 211 : ctx.xCur += last->width();
127 54383 : } else if (!secondToLast || secondToLast->isHBox() || startOfContinuousLayoutRegion) {
128 21423 : spaceMeasureGroup({ toMeasure(last) }, ctx);
129 : } else {
130 47242 : ctx.startMeas = toMeasure(last);
131 141726 : spaceMeasureGroup({ toMeasure(secondToLast), toMeasure(last) }, ctx);
132 : }
133 :
134 54594 : return ctx.xCur;
135 54783 : }
136 :
137 3 : void HorizontalSpacing::squeezeSystemToFit(System* system, double& curSysWidth, double targetSysWidth)
138 : {
139 5 : TRACEFUNC;
140 :
141 3 : Measure* firstMeasure = system->firstMeasure();
142 3 : if (!firstMeasure) {
143 0 : return;
144 : }
145 :
146 : static constexpr double STRETCH_REDUCTION_STEP = 0.33;
147 : static constexpr double SQUEEZE_STEP = 0.2;
148 :
149 3 : double squeezeFactor = 1 - SQUEEZE_STEP; // Reduces shape paddings
150 3 : double stretchReduction = 1 - STRETCH_REDUCTION_STEP; // Reduces duration stretch
151 :
152 7 : while (curSysWidth > targetSysWidth && muse::RealIsEqualOrMore(squeezeFactor, 0.0)) {
153 4 : curSysWidth = HorizontalSpacing::computeSpacingForFullSystem(system, stretchReduction, squeezeFactor, true);
154 4 : squeezeFactor -= SQUEEZE_STEP;
155 4 : stretchReduction -= STRETCH_REDUCTION_STEP;
156 : }
157 :
158 3 : if (curSysWidth < targetSysWidth) {
159 3 : return;
160 : }
161 :
162 : // Things don't fit without collisions, so give up and allow collisions
163 0 : double smallerStep = 0.25 * SQUEEZE_STEP;
164 0 : double widthReduction = 1 - smallerStep;
165 0 : while (curSysWidth > targetSysWidth && muse::RealIsEqualOrMore(widthReduction, 0.0)) {
166 0 : for (MeasureBase* mb : system->measures()) {
167 0 : if (!mb->isMeasure()) {
168 0 : continue;
169 : }
170 :
171 0 : Measure* m = toMeasure(mb);
172 0 : double prevWidth = m->width();
173 0 : for (Segment& segment : m->segments()) {
174 0 : if (!segment.isChordRestType()) {
175 0 : continue;
176 : }
177 0 : double curSegmentWidth = segment.width();
178 0 : segment.setWidth(curSegmentWidth * widthReduction);
179 : }
180 0 : m->respaceSegments();
181 0 : curSysWidth += m->width() - prevWidth;
182 : }
183 0 : widthReduction -= smallerStep;
184 : }
185 3 : }
186 :
187 6144 : void HorizontalSpacing::justifySystem(System* system, double curSysWidth, double targetSystemWidth)
188 : {
189 6144 : double rest = targetSystemWidth - curSysWidth;
190 6144 : if (muse::RealIsNull(rest)) {
191 0 : return;
192 : }
193 :
194 6144 : IF_ASSERT_FAILED(rest > 0) {
195 0 : return;
196 : }
197 :
198 6144 : std::vector<Spring> springs;
199 :
200 35424 : for (MeasureBase* mb : system->measures()) {
201 29280 : if (!mb->isMeasure()) {
202 139 : continue;
203 : }
204 138247 : for (Segment& s : toMeasure(mb)->segments()) {
205 109106 : if (s.isChordRestType() && s.ticks() > Fraction(0, 1) && s.visible() && s.enabled() && !s.allElementsInvisible()) {
206 60188 : double springConst = 1 / s.stretch();
207 60188 : double width = s.width() - s.widthOffset();
208 60188 : double preTension = width * springConst;
209 60188 : springs.emplace_back(springConst, width, preTension, &s);
210 : }
211 : }
212 : }
213 :
214 6144 : stretchSegmentsToWidth(springs, rest);
215 :
216 6144 : double x = system->leftMargin();
217 35424 : for (MeasureBase* mb : system->measures()) {
218 29280 : if (mb->isMeasure()) {
219 29141 : toMeasure(mb)->respaceSegments();
220 : }
221 29280 : mb->mutldata()->setPosX(x);
222 29280 : x += mb->width();
223 : }
224 6144 : }
225 :
226 61716 : void HorizontalSpacing::spaceMeasureGroup(const std::vector<Measure*>& measureGroup, HorizontalSpacingContext& ctx)
227 : {
228 61716 : if (measureGroup.empty()) {
229 110 : return;
230 : }
231 :
232 61606 : measureGroup.front()->mutldata()->setPosX(ctx.xCur);
233 :
234 61606 : std::vector<Segment*> segList;
235 61606 : segList.reserve(2 * measureGroup.size());
236 :
237 61606 : int startSegIdx = -1;
238 193526 : for (Measure* measure : measureGroup) {
239 131920 : if (measure == ctx.startMeas) {
240 47242 : startSegIdx = int(segList.size());
241 : }
242 611657 : for (Segment& segment : measure->segments()) {
243 479737 : segList.push_back(&segment);
244 : }
245 : }
246 :
247 61606 : IF_ASSERT_FAILED(!segList.empty()) {
248 0 : return;
249 : }
250 :
251 61606 : std::vector<SegmentPosition> segmentPositions = spaceSegments(segList, startSegIdx, ctx);
252 61606 : moveRightAlignedSegments(segmentPositions, ctx);
253 :
254 61606 : setPositionsAndWidths(segmentPositions);
255 :
256 61606 : if (!ctx.overrideMinMeasureWidth) {
257 61602 : enforceMinimumMeasureWidths(measureGroup);
258 : }
259 :
260 61606 : Measure* lastMeas = measureGroup.back();
261 61606 : ctx.xCur = lastMeas->x() + lastMeas->width();
262 61606 : }
263 :
264 61606 : std::vector<HorizontalSpacing::SegmentPosition> HorizontalSpacing::spaceSegments(const std::vector<Segment*>& segList, int startSegIdx,
265 : HorizontalSpacingContext& ctx)
266 : {
267 61606 : std::vector<SegmentPosition> placedSegments;
268 61606 : placedSegments.reserve(segList.size());
269 :
270 541343 : for (size_t i = 0; i < segList.size(); ++i) {
271 479737 : Segment* curSeg = segList[i];
272 479737 : if (ignoreSegmentForSpacing(curSeg)) {
273 29088 : placedSegments.emplace_back(curSeg, ctx.xCur, /*ignoreForSpacing*/ true);
274 191872 : continue;
275 : }
276 :
277 450649 : if (static_cast<int>(i) < startSegIdx) {
278 162784 : placedSegments.emplace_back(curSeg, curSeg->xPosInSystemCoords());
279 162784 : ctx.xCur = curSeg->xPosInSystemCoords() + curSeg->width();
280 162784 : continue;
281 : }
282 :
283 287865 : curSeg->clearWidthOffset();
284 :
285 287865 : if (placedSegments.empty() || ignoreAllSegmentsForSpacing(placedSegments)) {
286 14364 : double xFirst = getFirstSegmentXPos(curSeg, ctx);
287 14364 : ctx.xCur += xFirst;
288 14364 : placedSegments.emplace_back(curSeg, ctx.xCur);
289 : } else {
290 273501 : spaceAgainstPreviousSegments(curSeg, placedSegments, ctx);
291 : }
292 :
293 287865 : double leadingSpace = curSeg->extraLeadingSpace().toAbsolute(ctx.spatium);
294 287865 : placedSegments.back().xPosInSystemCoords += leadingSpace;
295 :
296 287865 : if (curSeg->isChordRestType()) {
297 171215 : bool isFirstCROfSystem = curSeg->rtick().isZero() && curSeg->measure()->isFirstInSystem();
298 171215 : if (isFirstCROfSystem) {
299 14273 : double xMinSystemHeaderDist = ctx.system->leftMargin() + curSeg->style().styleAbsolute(
300 14273 : Sid::systemHeaderMinStartOfSystemDistance);
301 14273 : placedSegments.back().xPosInSystemCoords = std::max(placedSegments.back().xPosInSystemCoords, xMinSystemHeaderDist);
302 14273 : ctx.xCur = std::max(ctx.xCur, xMinSystemHeaderDist);
303 : }
304 :
305 171215 : double chordRestSegWidth = chordRestSegmentNaturalWidth(curSeg, ctx);
306 :
307 171215 : Segment* nextSeg = i < segList.size() - 1 ? segList[i + 1] : nullptr;
308 171215 : if (nextSeg) {
309 171215 : double nextSegLeadingSpace = nextSeg->extraLeadingSpace().toAbsolute(ctx.spatium);
310 171215 : if (!muse::RealIsNull(nextSegLeadingSpace)) {
311 0 : nextSegLeadingSpace = std::max(nextSegLeadingSpace, -chordRestSegWidth);
312 0 : curSeg->addWidthOffset(nextSegLeadingSpace);
313 : }
314 171215 : if (nextSeg->isChordRestType()) {
315 85929 : applyCrossBeamSpacingCorrection(curSeg, nextSeg, chordRestSegWidth);
316 : }
317 : }
318 :
319 171215 : ctx.xCur += chordRestSegWidth;
320 : }
321 : }
322 :
323 61606 : if (ctx.systemIsFull) {
324 7223 : checkLyricsAgainstRightMargin(placedSegments, ctx);
325 7223 : checkLargeTimeSigAgainstRightMargin(placedSegments);
326 : }
327 :
328 61606 : return placedSegments;
329 0 : }
330 :
331 479737 : bool HorizontalSpacing::ignoreSegmentForSpacing(const Segment* segment)
332 : {
333 479737 : return !segment->enabled() || !segment->isActive() || segment->allElementsInvisible();
334 : }
335 :
336 273552 : bool HorizontalSpacing::ignoreAllSegmentsForSpacing(const std::vector<SegmentPosition>& segmentPositions)
337 : {
338 275868 : for (const SegmentPosition& segPos : segmentPositions) {
339 275817 : if (!segPos.ignoreForSpacing) {
340 273501 : return false;
341 : }
342 : }
343 :
344 51 : return true;
345 : }
346 :
347 273501 : void HorizontalSpacing::spaceAgainstPreviousSegments(Segment* segment, std::vector<SegmentPosition>& prevSegPositions,
348 : HorizontalSpacingContext& ctx)
349 : {
350 273501 : double x = -DBL_MAX;
351 273501 : int prevCRSegmentsCount = 0;
352 :
353 273501 : if (segment->measure()->isFirstInSystem()) {
354 70501 : checkLyricsAgainstLeftMargin(segment, x, ctx);
355 : }
356 :
357 273501 : bool segmentHasTimeSigAboveStaff = segment->hasTimeSigAboveStaves();
358 :
359 694482 : for (size_t i = prevSegPositions.size(); i > 0; --i) {
360 636734 : const SegmentPosition& prevSegPos = prevSegPositions[i - 1];
361 636734 : if (prevSegPos.ignoreForSpacing) {
362 43116 : continue;
363 : }
364 :
365 593618 : Segment* prevSeg = prevSegPos.segment;
366 593618 : double xPrevSeg = prevSegPos.xPosInSystemCoords;
367 :
368 593618 : if (prevSeg->isChordRestType()) {
369 356236 : ++prevCRSegmentsCount;
370 : }
371 :
372 593618 : bool timeSigAboveBarlineCase = segmentHasTimeSigAboveStaff && prevSeg->isEndBarLineType() && prevSeg->tick() == segment->tick();
373 0 : bool timeSigAboveKeySigCase = segmentHasTimeSigAboveStaff && prevSeg->isType(SegmentType::KeySig)
374 593618 : && prevSeg->tick() == segment->tick();
375 0 : bool timeSigAboveRepeatKeySigCase = segmentHasTimeSigAboveStaff && prevSeg->isType(SegmentType::KeySigRepeatAnnounce)
376 593618 : && prevSeg->tick() == segment->tick();
377 :
378 593618 : if (timeSigAboveBarlineCase) {
379 0 : x = xPrevSeg + prevSeg->minRight() - 0.5 * prevSeg->style().styleAbsolute(Sid::barWidth); // align to the preceding barline
380 593618 : } else if (timeSigAboveKeySigCase) {
381 0 : x = xPrevSeg + segment->minLeft(); // align to the preceding keySig
382 593618 : } else if (timeSigAboveRepeatKeySigCase) {
383 0 : x = xPrevSeg + prevSeg->minRight();
384 : } else {
385 593618 : double minHorDist = minHorizontalDistance(prevSeg, segment, ctx.squeezeFactor);
386 593618 : minHorDist = std::max(minHorDist, spaceLyricsAgainstBarlines(prevSeg, segment, ctx));
387 593618 : double xNonCollision = xPrevSeg + minHorDist;
388 593618 : x = std::max(x, xNonCollision);
389 : }
390 :
391 593618 : if (x > ctx.xCur || timeSigAboveBarlineCase) {
392 124526 : if (prevCRSegmentsCount > 0) {
393 23932 : double spaceIncrease = (x - ctx.xCur) / prevCRSegmentsCount;
394 23932 : double xMovement = spaceIncrease;
395 24508 : for (size_t j = i; j < prevSegPositions.size(); ++j) {
396 576 : SegmentPosition& segPos = prevSegPositions[j];
397 576 : if (segPos.ignoreForSpacing) {
398 85 : continue;
399 : }
400 491 : segPos.xPosInSystemCoords += xMovement;
401 491 : if (segPos.segment->isChordRestType()) {
402 98 : xMovement += spaceIncrease;
403 : }
404 : }
405 : }
406 :
407 124526 : ctx.xCur = x;
408 : }
409 :
410 593618 : if (stopCheckingPreviousSegments(prevSegPos, SegmentPosition(segment, ctx.xCur))) {
411 215753 : break;
412 : }
413 : }
414 :
415 273501 : double xSeg = segment->isRightAligned() ? x : ctx.xCur;
416 :
417 273501 : prevSegPositions.emplace_back(segment, xSeg);
418 273501 : }
419 :
420 593618 : bool HorizontalSpacing::stopCheckingPreviousSegments(const SegmentPosition& prevSegPos, const SegmentPosition& curSegPos)
421 : {
422 593618 : Segment* prevSeg = prevSegPos.segment;
423 593618 : Segment* curSeg = curSegPos.segment;
424 :
425 593618 : Fraction prevSegEndTick = prevSeg->tick() + prevSeg->ticks();
426 593618 : Fraction curSegTick = curSeg->tick();
427 :
428 593618 : if (prevSegEndTick >= curSegTick || curSeg->hasTimeSigAboveStaves()) {
429 369454 : return false;
430 : }
431 :
432 224164 : if (!curSeg->isChordRestType()) {
433 88652 : return true;
434 : }
435 :
436 135512 : bool curHasLyricsOrHarmony = false;
437 135512 : double curX = curSegPos.xPosInSystemCoords;
438 135512 : double curMinLeft = DBL_MAX;
439 323710 : for (const Shape& shape : curSeg->shapes()) {
440 603391 : for (const ShapeElement& shapeEl : shape.elements()) {
441 415193 : curMinLeft = std::min(curMinLeft, shapeEl.left());
442 415193 : if (const EngravingItem* item = shapeEl.item()) {
443 415193 : if (item->isLyrics() || item->isHarmony()) {
444 14875 : curHasLyricsOrHarmony = true;
445 : }
446 : }
447 : }
448 135512 : }
449 135512 : curMinLeft += curX;
450 :
451 135512 : bool prevHasLyricsOrHarmony = false;
452 135512 : double prevX = prevSegPos.xPosInSystemCoords;
453 135512 : double prevMinRight = -DBL_MAX;
454 323710 : for (const Shape& shape : prevSeg->shapes()) {
455 578762 : for (const ShapeElement& shapeEl : shape.elements()) {
456 390564 : prevMinRight = std::max(prevMinRight, shapeEl.right());
457 390564 : if (const EngravingItem* item = shapeEl.item()) {
458 390564 : if (item->isLyrics() || item->isHarmony()) {
459 6478 : prevHasLyricsOrHarmony = true;
460 : }
461 : }
462 : }
463 135512 : }
464 135512 : prevMinRight += prevX;
465 :
466 135512 : if (curHasLyricsOrHarmony == prevHasLyricsOrHarmony && curMinLeft > prevMinRight) {
467 125789 : return true;
468 : }
469 :
470 9723 : if (prevSeg->measure() != curSeg->measure() && prevSeg->measure() != curSeg->measure()->prevMM()) {
471 1312 : return true;
472 : }
473 :
474 8411 : return false;
475 : }
476 :
477 70501 : void HorizontalSpacing::checkLyricsAgainstLeftMargin(Segment* segment, double& x, HorizontalSpacingContext& ctx)
478 : {
479 178485 : for (const Shape& staffShape : segment->shapes()) {
480 322623 : for (const ShapeElement& shapeEl : staffShape.elements()) {
481 214639 : if (shapeEl.item() && shapeEl.item()->isLyrics()) {
482 3026 : x = std::max(x, ctx.xLeftBarrier - shapeEl.left());
483 : }
484 : }
485 70501 : }
486 70501 : }
487 :
488 7223 : void HorizontalSpacing::checkLyricsAgainstRightMargin(std::vector<SegmentPosition>& segPositions, const HorizontalSpacingContext& ctx)
489 : {
490 7223 : const double systemEdge = segPositions.back().xPosInSystemCoords + segPositions.back().segment->minRight();
491 7223 : const MStyle& style = ctx.system->style();
492 7223 : const bool lyricsDashForce = style.styleB(Sid::lyricsDashForce);
493 7223 : const bool lyricsMelismaForce = style.styleB(Sid::lyricsMelismaForce);
494 7223 : const double minSpaceForDash = lyricsDashForce ? style.styleAbsolute(Sid::lyricsDashPad) + style.styleAbsolute(Sid::lyricsDashMinLength)
495 7161 : + style.styleAbsolute(Sid::lineEndToBarlineDistance) + segPositions.back().segment->minRight() : 0.0;
496 7223 : const double minSpaceForMelisma = lyricsMelismaForce ? style.styleAbsolute(Sid::lyricsMelismaPad) + style.styleAbsolute(
497 : Sid::lyricsMelismaMinLength)
498 0 : + style.styleAbsolute(Sid::lineEndToBarlineDistance) + segPositions.back().segment->minRight() : 0.0;
499 :
500 7223 : int chordRestSegmentsCount = 0;
501 :
502 36587 : for (size_t i = segPositions.size(); i > 1; --i) {
503 35116 : SegmentPosition& segPos = segPositions[i - 1];
504 35116 : double x = segPos.xPosInSystemCoords;
505 35116 : Segment* seg = segPos.segment;
506 35116 : if (!seg->measure()->isLastInSystem()) {
507 5752 : break;
508 : }
509 29364 : if (seg->isChordRestType()) {
510 18574 : ++chordRestSegmentsCount;
511 : } else {
512 10790 : continue;
513 : }
514 18574 : double xMaxLyrics = x;
515 44848 : for (const Shape& shape : seg->shapes()) {
516 84728 : for (const ShapeElement& shapeEl : shape.elements()) {
517 58454 : if (shapeEl.item() && shapeEl.item()->isLyrics()) {
518 1315 : const Lyrics* lyrics = toLyrics(shapeEl.item());
519 1315 : bool hasMelisma = lyrics->separator() && lyrics->separator()->isEndMelisma();
520 1315 : bool hasDash = lyrics->syllabic() == LyricsSyllabic::BEGIN || lyrics->syllabic() == LyricsSyllabic::MIDDLE;
521 1315 : double rightEdge = x + shapeEl.right();
522 1315 : if (hasDash) {
523 20 : rightEdge += minSpaceForDash;
524 1295 : } else if (hasMelisma) {
525 16 : rightEdge += minSpaceForMelisma;
526 : }
527 1315 : xMaxLyrics = std::max(xMaxLyrics, rightEdge);
528 : }
529 : }
530 18574 : }
531 18574 : if (xMaxLyrics > systemEdge) {
532 110 : double lyricsOvershoot = xMaxLyrics - systemEdge;
533 110 : double spaceIncrease = lyricsOvershoot / chordRestSegmentsCount;
534 110 : double xMovement = spaceIncrease;
535 220 : for (size_t j = i; j < segPositions.size(); ++j) {
536 110 : SegmentPosition& sp = segPositions[j];
537 110 : sp.xPosInSystemCoords += xMovement;
538 110 : if (sp.segment->isChordRestType()) {
539 0 : xMovement += spaceIncrease;
540 : }
541 : }
542 : }
543 : }
544 7223 : }
545 :
546 593618 : double HorizontalSpacing::spaceLyricsAgainstBarlines(Segment* firstSeg, Segment* secondSeg, const HorizontalSpacingContext& ctx)
547 : {
548 949854 : if (!(firstSeg->isType(SegmentType::ChordRest) && secondSeg->isType(SegmentType::BarLineTypes))
549 949854 : && !(firstSeg->isType(SegmentType::BarLineTypes) && secondSeg->isType(SegmentType::ChordRest))) {
550 377847 : return 0.0;
551 : }
552 :
553 215771 : double w = 0.0;
554 :
555 215771 : bool crSegIsBefore = firstSeg->isChordRestType();
556 215771 : Segment* crSegment = crSegIsBefore ? firstSeg : secondSeg;
557 215771 : Segment* barlineSegment = crSegIsBefore ? secondSeg : firstSeg;
558 :
559 215771 : staff_idx_t nstaves = crSegment->score()->nstaves();
560 :
561 532748 : for (staff_idx_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
562 316977 : if (!ctx.system->staff(staffIdx)->show()) {
563 266243 : continue;
564 : }
565 :
566 311915 : staff_idx_t nextStaff = ctx.system->nextVisibleStaff(staffIdx);
567 311915 : if (nextStaff == muse::nidx) {
568 215771 : continue;
569 : }
570 :
571 96144 : BarLine* barline = toBarLine(barlineSegment->element(staff2track(staffIdx)));
572 96144 : if (!barline || !barline->spanStaff()) {
573 45410 : continue;
574 : }
575 :
576 50734 : Shape barlineShape = barline->shape().translate(barline->pos());
577 :
578 50734 : track_idx_t startTrack = staff2track(staffIdx);
579 50734 : track_idx_t endTrack = staff2track(nextStaff) + VOICES;
580 456778 : for (track_idx_t track = startTrack; track < endTrack; ++track) {
581 406044 : ChordRest* chordRest = toChordRest(crSegment->element(track));
582 406044 : if (!chordRest) {
583 311323 : continue;
584 : }
585 :
586 99501 : for (Lyrics* lyrics : chordRest->lyrics()) {
587 4780 : if (!(lyrics->avoidBarlines() && lyrics->visible() && lyrics->autoplace())) {
588 0 : continue;
589 : }
590 4780 : staff_idx_t lyricsStaffIdx = lyrics->staffIdx();
591 4780 : if ((lyricsStaffIdx == staffIdx && lyrics->placeBelow()) || (lyricsStaffIdx == nextStaff && lyrics->placeAbove())) {
592 1808 : Shape lyricsShape = lyrics->shape().translate(lyrics->pos());
593 1808 : double minDist = crSegIsBefore ? lyricsShape.right() - barlineShape.left() : barlineShape.right() - lyricsShape.left();
594 1808 : const double padding = 0.3 * lyrics->fontMetrics().xHeight();
595 1808 : minDist += padding;
596 1808 : w = std::max(w, minDist);
597 1808 : }
598 : }
599 : }
600 50734 : }
601 :
602 215771 : return w;
603 : }
604 :
605 7223 : void HorizontalSpacing::checkLargeTimeSigAgainstRightMargin(std::vector<SegmentPosition>& segPositions)
606 : {
607 7223 : SegmentPosition& cautionaryTSSegPos = segPositions.back();
608 7223 : if (!cautionaryTSSegPos.segment->hasTimeSigAboveStaves()) {
609 7223 : return;
610 : }
611 :
612 0 : const MStyle& style = cautionaryTSSegPos.segment->style();
613 0 : TimeSigVSMargin timeSigVSMargin = style.styleB(Sid::timeSigCenterOnBarline)
614 0 : ? style.styleV(Sid::timeSigVSMarginCentered).value<TimeSigVSMargin>()
615 0 : : style.styleV(Sid::timeSigVSMarginNonCentered).value<TimeSigVSMargin>();
616 0 : if (timeSigVSMargin != TimeSigVSMargin::RIGHT_ALIGN_TO_BARLINE) {
617 0 : return;
618 : }
619 :
620 0 : double xMax = 0.0;
621 0 : for (auto iter = segPositions.rbegin(); iter != segPositions.rend(); ++iter) {
622 0 : if (iter->segment->isType(SegmentType::EndBarLine | SegmentType::KeySigAnnounce)) {
623 0 : xMax = std::max(xMax, iter->xPosInSystemCoords + iter->segment->minRight());
624 : }
625 : }
626 :
627 0 : if (muse::RealIsNull(xMax)) {
628 0 : return;
629 : }
630 :
631 0 : double curTSPos = cautionaryTSSegPos.xPosInSystemCoords;
632 0 : double xMaxPos = xMax - cautionaryTSSegPos.segment->minRight();
633 0 : if (xMaxPos < curTSPos) {
634 0 : cautionaryTSSegPos.xPosInSystemCoords = xMaxPos;
635 : }
636 : }
637 :
638 61606 : void HorizontalSpacing::moveRightAlignedSegments(std::vector<SegmentPosition>& placedSegments, const HorizontalSpacingContext& ctx)
639 : {
640 541343 : for (size_t i = 0; i < placedSegments.size(); ++i) {
641 479737 : Segment* segment = placedSegments[i].segment;
642 479737 : if (!segment->isRightAligned()) {
643 477988 : continue;
644 : }
645 :
646 1749 : double x = DBL_MAX;
647 :
648 6217 : for (size_t j = i + 1; j < placedSegments.size(); ++j) {
649 5402 : SegmentPosition& segPos = placedSegments[j];
650 5402 : if (segPos.ignoreForSpacing) {
651 152 : continue;
652 : }
653 :
654 5250 : Segment* followingSeg = segPos.segment;
655 5250 : if (followingSeg->hasTimeSigAboveStaves()) {
656 0 : continue;
657 : }
658 5250 : if (followingSeg->measure() != segment->measure() && followingSeg->rtick().isNotZero()) {
659 934 : break;
660 : }
661 4316 : double followingSegX = segPos.xPosInSystemCoords;
662 4316 : double minDist = minHorizontalDistance(segment, followingSeg, ctx.squeezeFactor);
663 :
664 4316 : if (followingSeg->isRightAligned() && muse::RealIsNull(minDist)) {
665 49 : continue;
666 : }
667 :
668 4267 : x = std::min(x, followingSegX - minDist);
669 : }
670 :
671 1749 : if (x != DBL_MAX) {
672 1749 : placedSegments[i].xPosInSystemCoords = x;
673 1749 : SegmentPosition& nextSegPos = placedSegments[i + 1];
674 1763 : for (size_t j = i + 2; nextSegPos.ignoreForSpacing && j < placedSegments.size(); ++j) {
675 14 : nextSegPos = placedSegments[j];
676 : }
677 1749 : double nextSegX = nextSegPos.xPosInSystemCoords;
678 1749 : Segment* prevCRSeg = segment->prev(SegmentType::ChordRest);
679 1749 : if (prevCRSeg) {
680 1712 : prevCRSeg->addWidthOffset(-nextSegX + x);
681 : }
682 : }
683 : }
684 61606 : }
685 :
686 330768 : void HorizontalSpacing::checkCollisionsWithCrossStaffStems(const Segment* thisSeg, const Segment* nextSeg, staff_idx_t staffIdx,
687 : double& curMinDist)
688 : {
689 330768 : std::vector<ChordRest*> itemsToCheck;
690 330768 : itemsToCheck.reserve(VOICES);
691 :
692 2676536 : for (EngravingItem* el : nextSeg->elist()) {
693 2345768 : if (el && el->vStaffIdx() == staffIdx) {
694 326065 : ChordRest* cr = toChordRest(el);
695 326065 : if (cr->beam() && cr->beam()->cross()) {
696 172 : itemsToCheck.push_back(toChordRest(el));
697 : }
698 : }
699 : }
700 :
701 330768 : for (ChordRest* cr : itemsToCheck) {
702 172 : Chord* potentialCollidingChord = nullptr;
703 475 : for (ChordRest* beamElement : cr->beam()->elements()) {
704 409 : if (beamElement->isChord() && beamElement->parent() == thisSeg) {
705 106 : potentialCollidingChord = toChord(beamElement);
706 106 : break;
707 : }
708 : }
709 :
710 172 : if (!potentialCollidingChord || potentialCollidingChord->up() != cr->up() || !potentialCollidingChord->stem()) {
711 172 : return;
712 : }
713 :
714 46 : bool checkStemCollision = (cr->up() && potentialCollidingChord->vStaffIdx() > cr->vStaffIdx())
715 46 : || (!cr->up() && potentialCollidingChord->vStaffIdx() < cr->vStaffIdx());
716 30 : if (!checkStemCollision) {
717 30 : return;
718 : }
719 :
720 0 : Stem* stem = potentialCollidingChord->stem();
721 0 : Shape prevStemShape = stem->shape().translated(stem->pos() + potentialCollidingChord->pos());
722 : // We don't know where this stem is vertically, but we know from the beam arrangement that
723 : // it's going to be crossed anyway, so we can space to it as if it had infinite height.
724 0 : prevStemShape.adjust(0.0, -100000, 0.0, 100000);
725 :
726 0 : const Shape& staffShape = nextSeg->staffShape(staffIdx);
727 :
728 0 : double minDist = minHorizontalDistance(prevStemShape, staffShape, stem->spatium());
729 :
730 0 : curMinDist = std::max(curMinDist, minDist);
731 0 : }
732 330768 : }
733 :
734 171215 : double HorizontalSpacing::chordRestSegmentNaturalWidth(Segment* segment, HorizontalSpacingContext& ctx)
735 : {
736 171215 : double durationStretch = computeSegmentDurationStretch(segment, segment->prev(SegmentType::ChordRest));
737 :
738 171215 : Measure* measure = segment->measure();
739 171215 : double userStretch = std::clamp(measure->userStretch(), 0.1, 10.0); // TODO: enforce via UI, not here
740 :
741 171215 : double segTotalStretch = durationStretch * userStretch;
742 171215 : segment->setStretch(segTotalStretch);
743 :
744 : static constexpr Spatium DEFAULT_QUARTER_NOTE_SPACE = 3.5_sp;
745 171215 : double defaultQuarterNoteSpace = DEFAULT_QUARTER_NOTE_SPACE.toAbsolute(measure->spatium());
746 :
747 171215 : double naturalWidth = defaultQuarterNoteSpace * segTotalStretch * ctx.stretchReduction / ctx.spacingDensity;
748 :
749 171215 : return naturalWidth;
750 : }
751 :
752 171215 : double HorizontalSpacing::computeSegmentDurationStretch(const Segment* curSeg, const Segment* prevSeg)
753 : {
754 171215 : if (curSeg->isMMRestSegment()) {
755 409 : return durationStretchForMMRests(curSeg);
756 : }
757 :
758 170806 : Fraction segTicks = curSeg->ticks();
759 170806 : Fraction shortestCR = curSeg->shortestChordRest();
760 170806 : Fraction prevShortestCR = prevSeg ? prevSeg->shortestChordRest() : Fraction(0, 1);
761 :
762 170806 : bool hasAdjacent = curSeg->isChordRestType() && shortestCR == segTicks;
763 170806 : bool prevHasAdjacent = prevSeg && (prevSeg->isChordRestType() && prevShortestCR == prevSeg->ticks());
764 :
765 : double durStretch;
766 170806 : const MStyle& style = curSeg->style();
767 170806 : double slope = style.styleD(Sid::measureSpacing);
768 :
769 170806 : if (hasAdjacent || curSeg->measure()->isMMRest()) {
770 168860 : durStretch = durationStretchForTicks(slope, segTicks);
771 : } else {
772 : // Polyrythms
773 1946 : if (prevSeg && !prevHasAdjacent && prevShortestCR < shortestCR) {
774 546 : durStretch = durationStretchForTicks(slope, prevShortestCR) * (segTicks / prevShortestCR).toDouble();
775 : } else {
776 1400 : durStretch = durationStretchForTicks(slope, shortestCR) * (segTicks / shortestCR).toDouble();
777 : }
778 : }
779 :
780 170806 : if (style.styleB(Sid::scaleRythmicSpacingForSmallNotes) && needsCueSizeSpacing(curSeg)) {
781 670 : durStretch *= curSeg->style().styleD(Sid::smallNoteMag);
782 : }
783 :
784 170806 : return durStretch;
785 : }
786 :
787 409 : double HorizontalSpacing::durationStretchForMMRests(const Segment* segment)
788 : {
789 : static constexpr Fraction QUARTER = Fraction(1, 4);
790 : static constexpr int MIN_MMREST_COUNT = 2;
791 :
792 409 : const MStyle& style = segment->style();
793 409 : const Measure* measure = segment->measure();
794 :
795 409 : Fraction timeSig = measure->timesig();
796 409 : bool constantWidth = style.styleB(Sid::mmRestConstantWidth);
797 409 : int mmRestWidthIncrementCap = style.styleI(Sid::mmRestMaxWidthIncrease);
798 :
799 409 : Fraction baseDuration = constantWidth ? style.styleI(Sid::mmRestReferenceWidth) * QUARTER : timeSig;
800 409 : Fraction durationIncrement = constantWidth ? QUARTER : Fraction(1, timeSig.denominator());
801 409 : int incrementCount = std::max(measure->mmRestCount() - MIN_MMREST_COUNT, 0);
802 409 : incrementCount = std::min(incrementCount, mmRestWidthIncrementCap);
803 :
804 409 : Fraction resultDuration = baseDuration + incrementCount * durationIncrement;
805 :
806 818 : return durationStretchForTicks(style.styleD(Sid::measureSpacing), resultDuration);
807 : }
808 :
809 171215 : double HorizontalSpacing::durationStretchForTicks(double slope, const Fraction& ticks)
810 : {
811 : static constexpr Fraction REFERENCE_DURATION = Fraction(1, 4);
812 :
813 171215 : Fraction durationRatio = ticks / REFERENCE_DURATION;
814 :
815 171215 : return pow(slope, log2(durationRatio.toDouble()));
816 : }
817 :
818 102908 : bool HorizontalSpacing::needsCueSizeSpacing(const Segment* segment)
819 : {
820 102908 : IF_ASSERT_FAILED(segment->isChordRestType()) {
821 0 : return false;
822 : }
823 :
824 102908 : bool hasCueSizedCR = false;
825 131349 : for (EngravingItem* item : segment->elist()) {
826 129526 : if (!item) {
827 24871 : continue;
828 : }
829 104655 : const ChordRest* cr = toChordRest(item);
830 104655 : if (cr->isSmall()) {
831 690 : hasCueSizedCR = true;
832 103965 : } else if (cr->actualTicks() == segment->ticks()) {
833 101085 : return false;
834 : }
835 : }
836 :
837 1823 : return hasCueSizedCR;
838 : }
839 :
840 85929 : void HorizontalSpacing::applyCrossBeamSpacingCorrection(Segment* thisSeg, Segment* nextSeg, double& width)
841 : {
842 85929 : CrossBeamSpacing crossBeamSpacing = computeCrossBeamSpacing(thisSeg, nextSeg);
843 :
844 85929 : const Score* score = thisSeg->score();
845 85929 : const PaddingTable& paddingTable = score->paddingTables().horizontalPaddingTable();
846 85929 : const MStyle& style = score->style();
847 :
848 85929 : double displacement = score->noteHeadWidth() - style.styleAbsolute(Sid::stemWidth);
849 :
850 85929 : if (crossBeamSpacing.upDown && crossBeamSpacing.canBeAdjusted) {
851 27 : thisSeg->addWidthOffset(displacement);
852 27 : width += displacement;
853 85902 : } else if (crossBeamSpacing.downUp && crossBeamSpacing.canBeAdjusted) {
854 23 : thisSeg->addWidthOffset(-displacement);
855 23 : width -= displacement;
856 : }
857 :
858 85929 : if (crossBeamSpacing.upDown) {
859 27 : if (crossBeamSpacing.hasOpposingBeamlets) {
860 0 : double minBeamletClearance = style.styleAbsolute(Sid::beamMinLen) * 2.0 + paddingTable.at(ElementType::BEAM).at(
861 0 : ElementType::BEAM);
862 0 : width = std::max(width, displacement + minBeamletClearance);
863 : } else {
864 27 : width = std::max(width, 2 * displacement);
865 : }
866 : }
867 :
868 85929 : if (crossBeamSpacing.preventCrossStaffKerning) {
869 38 : double padding = crossBeamSpacing.ensureMinStemDistance ? paddingTable.at(ElementType::STEM).at(ElementType::STEM)
870 11 : : style.styleAbsolute(Sid::minNoteDistance);
871 38 : width = std::max(width, score->noteHeadWidth() + padding);
872 85891 : } else if (crossBeamSpacing.ensureMinStemDistance) {
873 4 : width = std::max(width, paddingTable.at(ElementType::STEM).at(ElementType::STEM));
874 : }
875 85929 : }
876 :
877 328350 : double HorizontalSpacing::minStemDistOnNonAdjacentCross(const Segment* thisSeg, const Segment* nextSeg)
878 : {
879 : // Extreme edge-case of chords that are a) adjacent in a cross-staff beam but b) on non-adjacent segments
880 : // (which causes them to escape the previous cross-staff spacing checks) c) stemmed up->down (see #27786)
881 328350 : const PaddingTable& paddingTable = thisSeg->score()->paddingTables().horizontalPaddingTable();
882 328350 : double dist = -DBL_MAX;
883 2217078 : for (EngravingItem* item : nextSeg->elist()) {
884 1888728 : if (!item || !item->isChord()) {
885 1888701 : continue;
886 : }
887 :
888 200280 : Chord* chord = toChord(item);
889 200280 : if (!chord->stem() || chord->up() || !(chord->beam() && chord->beam()->cross())) {
890 200193 : continue;
891 : }
892 :
893 87 : Beam* beam = chord->beam();
894 87 : std::vector<ChordRest*> beamElements = beam->elements();
895 87 : Chord* prevChordOnBeam = nullptr;
896 218 : for (size_t i = 0; i < beamElements.size(); ++i) {
897 218 : if (beamElements[i] == chord) {
898 87 : if (i > 0) {
899 63 : ChordRest* prevChordRest = beamElements[i - 1];
900 63 : prevChordOnBeam = prevChordRest->isChord() ? toChord(prevChordRest) : nullptr;
901 : }
902 87 : break;
903 : }
904 : }
905 :
906 87 : if (!prevChordOnBeam || prevChordOnBeam->parent() != thisSeg || !prevChordOnBeam->up() || !prevChordOnBeam->stem()) {
907 60 : continue;
908 : }
909 :
910 27 : double minStemDist = prevChordOnBeam->x() + prevChordOnBeam->stem()->x() - (chord->x() + chord->stem()->x());
911 27 : minStemDist += paddingTable.at(ElementType::STEM).at(ElementType::STEM);
912 :
913 27 : dist = std::max(dist, minStemDist);
914 87 : }
915 :
916 328350 : return dist;
917 : }
918 :
919 85929 : HorizontalSpacing::CrossBeamSpacing HorizontalSpacing::computeCrossBeamSpacing(Segment* thisSeg, Segment* nextSeg)
920 : {
921 85929 : CrossBeamSpacing crossBeamType;
922 :
923 85929 : if (!thisSeg->isChordRestType() || !nextSeg || !nextSeg->isChordRestType()) {
924 0 : return crossBeamType;
925 : }
926 :
927 85929 : bool preventCrossStaffKerning = false;
928 85929 : bool ensureMinStemDistance = false;
929 550957 : for (EngravingItem* e : thisSeg->elist()) {
930 465028 : if (!e || !e->isChord() || !e->visible() || !e->staff()->visible()) {
931 371190 : continue;
932 : }
933 :
934 93838 : Chord* thisChord = toChord(e);
935 93838 : ChordRest* nextCR = toChordRest(nextSeg->element(thisChord->track()));
936 93838 : Chord* nextChord = nextCR && nextCR->isChord() ? toChord(nextCR) : nullptr;
937 93838 : if (!nextChord) {
938 18644 : continue;
939 : }
940 :
941 75194 : int thisStaffMove = thisChord->staffMove();
942 75194 : int nextStaffMove = nextChord->staffMove();
943 75194 : if (thisStaffMove == nextStaffMove) {
944 75126 : continue;
945 : }
946 :
947 68 : preventCrossStaffKerning = thisStaffMove > nextStaffMove;
948 106 : ensureMinStemDistance = (thisStaffMove > nextStaffMove && thisChord->up() && !nextChord->up())
949 106 : || (thisStaffMove < nextStaffMove && thisChord->up() == nextChord->up());
950 : }
951 :
952 85929 : crossBeamType.preventCrossStaffKerning = preventCrossStaffKerning;
953 85929 : crossBeamType.ensureMinStemDistance = ensureMinStemDistance;
954 :
955 85929 : bool upDown = false;
956 85929 : bool downUp = false;
957 85929 : bool canBeAdjusted = true;
958 85929 : bool hasOpposingBeamlets = false;
959 :
960 : // Spacing can be adjusted for cross-beam cases only if there aren't
961 : // chords in other voices in this or next segment.
962 440098 : for (EngravingItem* e : thisSeg->elist()) {
963 377147 : if (!e || !e->isChordRest() || !e->staff()->visible()) {
964 264709 : continue;
965 : }
966 112438 : ChordRest* thisCR = toChordRest(e);
967 112438 : if (!thisCR->visible() || thisCR->isFullMeasureRest() || (thisCR->isRest() && toRest(thisCR)->isGap())) {
968 3707 : continue;
969 : }
970 108731 : Beam* beam = thisCR->beam();
971 108731 : if (!beam && thisCR->actualTicks() <= thisSeg->ticks()) {
972 72347 : canBeAdjusted = false;
973 : }
974 108731 : if (!beam || (beam->elements().size() == 2 && thisCR->up())) {
975 79184 : continue;
976 : }
977 70556 : for (EngravingItem* ee : nextSeg->elist()) {
978 63987 : if (!ee || !ee->isChordRest() || !ee->staff()->visible()) {
979 30746 : continue;
980 : }
981 33241 : ChordRest* nextCR = toChordRest(ee);
982 33241 : if (!nextCR->visible() || nextCR->isFullMeasureRest() || (thisCR->isRest() && toRest(thisCR)->isGap())) {
983 16 : continue;
984 : }
985 33225 : if (!nextCR->beam()) {
986 6188 : canBeAdjusted = false;
987 6188 : continue;
988 : }
989 27037 : if (nextCR->beam() != beam) {
990 4009 : continue;
991 : }
992 23028 : if (thisCR->up() == nextCR->up()) {
993 22978 : return crossBeamType;
994 : }
995 50 : if (thisCR->up() && !nextCR->up()) {
996 27 : if (thisCR->beamlet() && nextCR->beamlet()) {
997 0 : hasOpposingBeamlets = true;
998 : }
999 27 : upDown = true;
1000 : }
1001 50 : if (!thisCR->up() && nextCR->up()) {
1002 23 : downUp = true;
1003 : }
1004 50 : if (upDown && downUp) {
1005 0 : return crossBeamType;
1006 : }
1007 : }
1008 : }
1009 :
1010 62951 : crossBeamType.upDown = upDown;
1011 62951 : crossBeamType.downUp = downUp;
1012 62951 : crossBeamType.canBeAdjusted = canBeAdjusted;
1013 62951 : crossBeamType.hasOpposingBeamlets = hasOpposingBeamlets;
1014 :
1015 62951 : return crossBeamType;
1016 : }
1017 :
1018 131313 : double HorizontalSpacing::computeMinMeasureWidth(Measure* m)
1019 : {
1020 131313 : const MStyle& style = m->style();
1021 131313 : double minWidth = style.styleAbsolute(Sid::minMeasureWidth);
1022 131313 : double maxSysWidth = m->system()->width() - m->system()->leftMargin();
1023 :
1024 131313 : if (maxSysWidth <= 0) {
1025 0 : maxSysWidth = minWidth;
1026 : }
1027 131313 : minWidth = std::min(minWidth, maxSysWidth);
1028 :
1029 131313 : if (m->ticks() < m->timesig()) {
1030 407 : minWidth *= (m->ticks() / m->timesig()).toDouble();
1031 : }
1032 :
1033 131313 : Segment* firstCRSegment = m->findFirstR(SegmentType::ChordRest, Fraction(0, 1));
1034 131313 : if (!firstCRSegment) {
1035 0 : return minWidth;
1036 : }
1037 131313 : if (firstCRSegment == m->firstEnabled()) {
1038 103232 : return minWidth;
1039 : }
1040 :
1041 28081 : double startPosition = firstCRSegment->x() - firstCRSegment->minLeft();
1042 28081 : if (firstCRSegment->hasAccidentals()) {
1043 1241 : startPosition -= style.styleAbsolute(Sid::barAccidentalDistance);
1044 : } else {
1045 26840 : startPosition -= style.styleAbsolute(Sid::barNoteDistance);
1046 : }
1047 :
1048 28081 : minWidth += startPosition;
1049 28081 : minWidth = std::min(minWidth, maxSysWidth);
1050 :
1051 28081 : return minWidth;
1052 : }
1053 :
1054 61602 : void HorizontalSpacing::enforceMinimumMeasureWidths(const std::vector<Measure*> measureGroup)
1055 : {
1056 193500 : for (size_t i = 0; i < measureGroup.size(); ++i) {
1057 131898 : Measure* measure = measureGroup[i];
1058 131898 : if (measure->isMMRest()) {
1059 585 : continue; // minimum mmRest width is already enforced during spacing
1060 : }
1061 131313 : double minWidth = computeMinMeasureWidth(measure);
1062 131313 : double diff = minWidth - measure->width();
1063 131313 : if (diff > 0) {
1064 3776 : stretchMeasureToTargetWidth(measure, minWidth);
1065 9047 : for (size_t j = i + 1; j < measureGroup.size(); ++j) {
1066 5271 : measureGroup[j]->mutldata()->moveX(diff);
1067 : }
1068 : }
1069 : }
1070 61602 : }
1071 :
1072 3776 : void HorizontalSpacing::stretchMeasureToTargetWidth(Measure* m, double targetWidth)
1073 : {
1074 3776 : IF_ASSERT_FAILED(targetWidth > m->width()) {
1075 0 : return;
1076 : }
1077 :
1078 3776 : std::vector<Spring> springs;
1079 12451 : for (Segment& s : m->segments()) {
1080 8675 : if (s.isChordRestType() && s.visible() && s.enabled() && !s.allElementsInvisible()) {
1081 3833 : double springConst = 1 / s.stretch();
1082 3833 : double width = s.width() - s.widthOffset();
1083 3833 : double preTension = width * springConst;
1084 3833 : springs.emplace_back(springConst, width, preTension, &s);
1085 : }
1086 : }
1087 :
1088 3776 : stretchSegmentsToWidth(springs, targetWidth - m->width());
1089 :
1090 3776 : m->respaceSegments();
1091 3776 : }
1092 :
1093 9920 : void HorizontalSpacing::stretchSegmentsToWidth(std::vector<Spring>& springs, double width)
1094 : {
1095 9920 : if (springs.empty() || muse::RealIsEqualOrLess(width, 0.0)) {
1096 6 : return;
1097 : }
1098 :
1099 189102 : std::sort(springs.begin(), springs.end(), [](const Spring& a, const Spring& b) { return a.preTension < b.preTension; });
1100 9914 : double inverseSpringConst = 0.0;
1101 9914 : double force = 0.0;
1102 :
1103 : //! NOTE springs.cbegin() != springs.cend() because of the emptiness check at the top
1104 9914 : auto spring = springs.cbegin();
1105 : do {
1106 61319 : inverseSpringConst += 1 / spring->springConst;
1107 61319 : width += spring->width;
1108 61319 : force = width / inverseSpringConst;
1109 61319 : ++spring;
1110 61319 : } while (spring != springs.cend() && !(force < spring->preTension));
1111 :
1112 73935 : for (const Spring& spring2 : springs) {
1113 64021 : if (force > spring2.preTension) {
1114 61319 : double newWidth = force / spring2.springConst;
1115 61319 : spring2.segment->setWidth(newWidth + spring2.segment->widthOffset());
1116 : }
1117 : }
1118 : }
1119 :
1120 61606 : void HorizontalSpacing::setPositionsAndWidths(const std::vector<SegmentPosition>& segmentPositions)
1121 : {
1122 61606 : size_t segmentsSize = segmentPositions.size();
1123 541343 : for (size_t i = 0; i < segmentsSize; ++i) {
1124 479737 : const SegmentPosition& segPos = segmentPositions[i];
1125 479737 : Segment* curSeg = segPos.segment;
1126 479737 : double curX = segPos.xPosInSystemCoords;
1127 :
1128 479737 : if (segPos.ignoreForSpacing) {
1129 29074 : curSeg->setWidth(0.0);
1130 29074 : continue;
1131 : }
1132 :
1133 450663 : Segment* nextSeg = curSeg;
1134 450663 : double nextX = curX;
1135 479084 : for (size_t j = i + 1; j < segmentsSize; ++j) {
1136 417478 : const SegmentPosition& nextSegPos = segmentPositions[j];
1137 417478 : if (!nextSegPos.ignoreForSpacing) {
1138 389057 : nextSeg = nextSegPos.segment;
1139 389057 : nextX = nextSegPos.xPosInSystemCoords;
1140 389057 : break;
1141 : }
1142 : }
1143 :
1144 450663 : Measure* curSegMeasure = curSeg->measure();
1145 450663 : Measure* nextSegMeasure = nextSeg->measure();
1146 :
1147 898022 : bool leadingNonCRException = ((nextSeg->isKeySigType() || nextSeg->isTimeSigType()
1148 898022 : || nextSeg->isClefType()) && !curSeg->isType(SegmentType::BarLineTypes));
1149 70314 : bool computeWidthByDifferenceFromNext = curSegMeasure == nextSegMeasure || nextSeg->isStartRepeatBarLineType()
1150 520977 : || leadingNonCRException;
1151 :
1152 450663 : double segWidth = computeWidthByDifferenceFromNext ? nextX - curX : curSeg->minRight();
1153 :
1154 450663 : curSeg->setWidth(segWidth);
1155 :
1156 450663 : if (curSegMeasure != nextSegMeasure) {
1157 70314 : curSegMeasure->setWidth(curX + segWidth - curSegMeasure->x());
1158 70314 : nextSegMeasure->mutldata()->setPosX(curX + segWidth);
1159 : }
1160 :
1161 450663 : curSeg->setXPosInSystemCoords(curX);
1162 :
1163 450663 : if (nextSeg == curSeg) {
1164 61620 : double nextSegWidth = 0.0;
1165 61620 : if (nextSeg->hasTimeSigAboveStaves()) {
1166 0 : assert(i > 0);
1167 0 : if (nextSeg->makeSpaceForTimeSigAboveStaves()) {
1168 0 : nextSegWidth = nextSeg->minRight();
1169 : }
1170 0 : Segment* prevSeg = segmentPositions[i - 1].segment;
1171 0 : double xDiff = segmentPositions[i - 1].xPosInSystemCoords - curX;
1172 0 : double minSpaceForPrevSeg = xDiff + prevSeg->minRight();
1173 0 : if (prevSeg->trailer()) {
1174 0 : minSpaceForPrevSeg += prevSeg->style().styleAbsolute(Sid::systemTrailerRightMargin);
1175 : }
1176 0 : nextSegWidth = std::max(nextSegWidth, minSpaceForPrevSeg);
1177 61620 : } else if (nextSeg->trailer()) {
1178 990 : nextSegWidth = nextSeg->minRight() + nextSeg->style().styleAbsolute(Sid::systemTrailerRightMargin);
1179 : } else {
1180 60630 : nextSegWidth = nextSeg->minRight();
1181 : }
1182 61620 : nextSeg->setWidth(nextSegWidth);
1183 61620 : nextSegMeasure->setWidth(nextX + nextSegWidth - nextSegMeasure->x());
1184 61620 : nextSeg->setXPosInSystemCoords(nextX);
1185 : }
1186 : }
1187 61606 : }
1188 :
1189 14364 : double HorizontalSpacing::getFirstSegmentXPos(Segment* segment, HorizontalSpacingContext& ctx)
1190 : {
1191 14364 : const MStyle& style = segment->style();
1192 :
1193 14364 : double x = 0.0;
1194 14364 : switch (segment->segmentType()) {
1195 4203 : case SegmentType::BeginBarLine:
1196 4203 : return 0.0;
1197 8 : case SegmentType::ChordRest:
1198 : {
1199 8 : Shape leftBarrier(RectF(0.0, -0.5 * DBL_MAX, 0.0, DBL_MAX));
1200 8 : x = minLeft(segment, leftBarrier);
1201 8 : x += style.styleAbsolute(segment->hasAccidentals() ? Sid::barAccidentalDistance : Sid::barNoteDistance);
1202 8 : x = std::max(x, style.styleAbsolute(Sid::systemHeaderMinStartOfSystemDistance));
1203 8 : break;
1204 8 : }
1205 10153 : case SegmentType::Clef:
1206 : case SegmentType::HeaderClef:
1207 10153 : x = style.styleAbsolute(Sid::clefLeftMargin);
1208 10153 : break;
1209 0 : case SegmentType::KeySig:
1210 0 : x = style.styleAbsolute(Sid::keysigLeftMargin);
1211 0 : break;
1212 0 : case SegmentType::TimeSig:
1213 0 : x = style.styleAbsolute(Sid::timesigLeftMargin);
1214 0 : break;
1215 0 : default:
1216 0 : x = 0.0;
1217 : }
1218 :
1219 10161 : return x * ctx.squeezeFactor;
1220 : }
1221 :
1222 861385 : double HorizontalSpacing::minHorizontalDistance(const Shape& f, const Shape& s, double spatium, double squeezeFactor)
1223 : {
1224 861385 : double dist = -DBL_MAX; // min real
1225 861385 : double absoluteMinPadding = 0.1 * spatium * squeezeFactor;
1226 2434313 : for (const ShapeElement& r2 : s.elements()) {
1227 1572928 : if (r2.isNull()) {
1228 2090 : continue;
1229 : }
1230 :
1231 1570838 : const EngravingItem* item2 = r2.item();
1232 1570838 : double by1 = r2.top();
1233 1570838 : double by2 = r2.bottom();
1234 5662785 : for (const ShapeElement& r1 : f.elements()) {
1235 4091947 : if (r1.isNull()) {
1236 3000609 : continue;
1237 : }
1238 :
1239 4090930 : const EngravingItem* item1 = r1.item();
1240 :
1241 4090930 : double ay1 = r1.top();
1242 4090930 : double ay2 = r1.bottom();
1243 4090930 : double verticalClearance = computeVerticalClearance(item1, item2, spatium) * squeezeFactor;
1244 4090930 : bool intersection = mu::engraving::intersects(ay1, ay2, by1, by2, verticalClearance);
1245 4090930 : double padding = 0;
1246 4090930 : KerningType kerningType = KerningType::NON_KERNING;
1247 4090930 : if (item1 && item2) {
1248 4090904 : padding = computePadding(item1, item2);
1249 4090904 : padding *= squeezeFactor;
1250 4090904 : padding = std::max(padding, absoluteMinPadding);
1251 4090904 : kerningType = computeKerning(item1, item2);
1252 : }
1253 :
1254 4090930 : if (kerningType == KerningType::ALLOW_COLLISION) {
1255 41889 : continue;
1256 : }
1257 :
1258 7006744 : if (kerningType == KerningType::NON_KERNING
1259 1678752 : || intersection
1260 1091338 : || (r1.width() == 0 || r2.width() == 0) // Temporary hack: shapes of zero-width are assumed to collide with everyghin
1261 5727793 : || (!item1 && item2 && item2->isLyrics())) {
1262 2957703 : dist = std::max(dist, r1.right() - r2.left() + padding);
1263 2957703 : continue;
1264 : }
1265 :
1266 1091338 : switch (kerningType) {
1267 0 : case KerningType::KERN_UNTIL_LEFT_EDGE:
1268 0 : dist = std::max(dist, r1.left() - r2.left());
1269 0 : break;
1270 0 : case KerningType::KERN_UNTIL_CENTER:
1271 0 : dist = std::max(dist, r1.left() + 0.5 * r1.width() - r2.left());
1272 0 : break;
1273 1965 : case KerningType::KERN_UNTIL_RIGHT_EDGE:
1274 1965 : dist = std::max(dist, r1.right() - r2.left());
1275 1965 : break;
1276 1089373 : default:
1277 1089373 : break;
1278 : }
1279 : }
1280 : }
1281 861385 : return dist;
1282 : }
1283 :
1284 : // Logic moved from Shape
1285 859495 : double HorizontalSpacing::shapeSpatium(const Shape& s)
1286 : {
1287 859503 : for (auto it = s.elements().begin(); it != s.elements().end(); ++it) {
1288 813917 : if (it->item()) {
1289 813909 : return it->item()->spatium();
1290 : }
1291 : }
1292 45586 : return 0.0;
1293 : }
1294 :
1295 : //---------------------------------------------------------
1296 : // minHorizontalDistance
1297 : // calculate the minimum layout distance to Segment ns
1298 : //---------------------------------------------------------
1299 :
1300 597934 : double HorizontalSpacing::minHorizontalDistance(const Segment* f, const Segment* ns, double squeezeFactor)
1301 : {
1302 597934 : if (f->segmentType() & SegmentType::BarLineTypes) {
1303 170157 : if (ns->isStartRepeatBarLineType()) {
1304 308 : if (f->isBeginBarLineType()) {
1305 62 : return 0.0;
1306 : }
1307 246 : double prevBarlineWidth = f->minRight();
1308 246 : double thickBarlineWidth = f->style().styleAbsolute(Sid::endBarWidth);
1309 246 : return std::max(prevBarlineWidth - thickBarlineWidth, 0.0);
1310 : }
1311 169849 : if (ns->isMMRestSegment()) {
1312 272 : return f->minRight() + f->style().styleAbsolute(Sid::barNoteDistance);
1313 : }
1314 : }
1315 :
1316 597354 : if (f->isHeaderClefType() && ns->hasTimeSigAboveStaves()) {
1317 0 : Segment* possibleKeySig = f->nextActive();
1318 0 : if (possibleKeySig && possibleKeySig->isKeySigType() && !ignoreSegmentForSpacing(possibleKeySig)) {
1319 0 : return 0.0;
1320 : }
1321 0 : return f->minRight() + ns->minLeft() + f->style().styleAbsolute(Sid::headerToLineStartDistance);
1322 : }
1323 :
1324 597354 : bool systemHeaderGap = needsHeaderSpacingExceptions(f, ns);
1325 :
1326 597354 : double ww = -DBL_MAX; // can remain negative
1327 597354 : double d = 0.0;
1328 597354 : Score* score = f->score();
1329 1465626 : for (unsigned staffIdx = 0; staffIdx < f->shapes().size(); ++staffIdx) {
1330 868272 : if (score->staff(staffIdx) && !score->staff(staffIdx)->show()) {
1331 9780 : continue;
1332 : }
1333 :
1334 858492 : const Shape& fshape = f->staffShape(staffIdx);
1335 858492 : double sp = shapeSpatium(fshape);
1336 858492 : d = minHorizontalDistance(fshape, ns->staffShape(staffIdx), sp, squeezeFactor);
1337 858492 : if (systemHeaderGap) {
1338 : // first chordrest of a staff should clear the widest header for any staff
1339 : // so make sure segment is as wide as it needs to be
1340 47764 : d = std::max(d, f->staffShape(staffIdx).right());
1341 : }
1342 :
1343 : // Multi-staff ChordBrackets are stored on their owner chords
1344 : // and are therefore not fully represented in every visually spanned staff Shape.
1345 : // Add the missing bracket collisions before combining the per-staff minimum distances.
1346 858492 : if (f->isChordRestType() || ns->isChordRestType()) {
1347 721763 : ChordBracketLayout::updateHorizontalSpacing(f, ns, staffIdx, squeezeFactor, d);
1348 : }
1349 :
1350 858492 : if (f->isChordRestType() && ns->isChordRestType()) {
1351 330768 : checkCollisionsWithCrossStaffStems(f, ns, staffIdx, d);
1352 : }
1353 :
1354 858492 : ww = std::max(ww, d);
1355 : }
1356 597354 : double w = std::max(ww, 0.0); // non-negative
1357 :
1358 : // Header exceptions that need additional space (more than the padding)
1359 597354 : double absoluteMinHeaderDist = 1.5 * f->spatium() * squeezeFactor;
1360 597354 : if (systemHeaderGap) {
1361 31787 : if (f->isTimeSigType()) {
1362 13155 : double timeSigHeaderDist = squeezeFactor * f->style().styleAbsolute(Sid::systemHeaderTimeSigDistance);
1363 13155 : w = std::max(w, f->minRight() + timeSigHeaderDist);
1364 : } else {
1365 18632 : double headerDist = squeezeFactor * f->style().styleAbsolute(Sid::systemHeaderDistance);
1366 18632 : w = std::max(w, f->minRight() + headerDist);
1367 : }
1368 31787 : if (ns && ns->isStartRepeatBarLineType()) {
1369 : // Align the thin barline of the start repeat to the header
1370 360 : w -= f->style().styleAbsolute(Sid::endBarWidth) + f->style().styleAbsolute(Sid::endBarDistance);
1371 : }
1372 31787 : double diff = w - f->minRight() - ns->minLeft();
1373 31787 : if (diff < absoluteMinHeaderDist) {
1374 2477 : w += absoluteMinHeaderDist - diff;
1375 : }
1376 : }
1377 :
1378 : // Multimeasure rest exceptions that need special handling
1379 597354 : if (f->measure() && f->measure()->isMMRest()) {
1380 2567 : const PaddingTable& paddingTable = f->score()->paddingTables().horizontalPaddingTable();
1381 2567 : if (ns->isChordRestType()) {
1382 1120 : double minDist = f->minRight();
1383 1120 : if (f->isClefType()) {
1384 6 : minDist += paddingTable.at(ElementType::CLEF).at(ElementType::REST);
1385 1114 : } else if (f->isKeySigType()) {
1386 21 : minDist += paddingTable.at(ElementType::KEYSIG).at(ElementType::REST);
1387 1093 : } else if (f->isTimeSigType()) {
1388 316 : minDist += paddingTable.at(ElementType::TIMESIG).at(ElementType::REST);
1389 : }
1390 1120 : w = std::max(w, minDist);
1391 1447 : } else if (f->isChordRestType()) {
1392 540 : double minWidth = f->style().styleAbsolute(Sid::minMMRestWidth);
1393 540 : if (!f->style().styleB(Sid::oldStyleMultiMeasureRests)) {
1394 540 : minWidth += f->style().styleAbsolute(Sid::multiMeasureRestMargin);
1395 : }
1396 540 : w = std::max(w, minWidth);
1397 : }
1398 : }
1399 :
1400 : // Allocate space to ensure minimum length of hanging ties or gliss at start of system
1401 : // These only occur when one segment is a ChordRest and the other isn't
1402 : // Allocate space to ensure minimum length of partial ties
1403 597354 : if (f->isChordRestType() == ns->isChordRestType()) {
1404 328350 : w = std::max(w, minStemDistOnNonAdjacentCross(f, ns));
1405 328350 : return w;
1406 : }
1407 :
1408 269004 : const bool repeatSeg = f->isStartRepeatBarLineType() || ns->isStartRepeatBarLineType();
1409 383876 : const bool endOfSystem = f->isChordRestType() && !(ns->isChordRestType() || ns->isStartRepeatBarLineType())
1410 383876 : && (f->measure()->isLastInSystem() || f->measure()->next()->isHBox());
1411 269004 : const bool systemEnd = repeatSeg || endOfSystem;
1412 :
1413 269004 : computeHangingLineWidth(f, ns, w, systemHeaderGap, systemEnd);
1414 :
1415 269004 : return w;
1416 : }
1417 :
1418 597354 : bool HorizontalSpacing::needsHeaderSpacingExceptions(const Segment* seg, const Segment* nextSeg)
1419 : {
1420 : static const std::unordered_set<SegmentType> HEADER_SEGMENT_TYPES = { SegmentType::HeaderClef,
1421 : SegmentType::KeySig,
1422 : SegmentType::KeySigStartRepeatAnnounce,
1423 : SegmentType::Ambitus,
1424 : SegmentType::TimeSig,
1425 597384 : SegmentType::TimeSigStartRepeatAnnounce };
1426 :
1427 597354 : return muse::contains(HEADER_SEGMENT_TYPES, seg->segmentType())
1428 660631 : && seg->rtick().isZero() && !seg->hasTimeSigAboveStaves()
1429 62997 : && (nextSeg->measure()->isFirstInSystem() || nextSeg->measure()->prev()->isHBox())
1430 1257985 : && (nextSeg->isStartRepeatBarLineType() || nextSeg->isChordRestType());
1431 : }
1432 :
1433 : //---------------------------------------------------------
1434 : // minLeft
1435 : // Calculate minimum distance needed to the left shape
1436 : // sl. Sl is the same for all staves.
1437 : //---------------------------------------------------------
1438 :
1439 8 : double HorizontalSpacing::minLeft(const Segment* seg, const Shape& ls)
1440 : {
1441 8 : double distance = 0.0;
1442 8 : double sp = shapeSpatium(ls);
1443 16 : for (const Shape& sh : seg->shapes()) {
1444 8 : double d = minHorizontalDistance(ls, sh, sp, 1.0);
1445 8 : if (d > distance) {
1446 0 : distance = d;
1447 : }
1448 : }
1449 8 : return distance;
1450 : }
1451 :
1452 4090904 : double HorizontalSpacing::computePadding(const EngravingItem* item1, const EngravingItem* item2)
1453 : {
1454 4090904 : if (item1->isParenthesis() || item2->isParenthesis()) {
1455 72047 : return ParenthesisLayout::computeParenthesisPadding(item1, item2);
1456 : }
1457 4018857 : const PaddingTable& paddingTable = item1->score()->paddingTables().horizontalPaddingTable();
1458 4018857 : ElementType type1 = item1->type();
1459 4018857 : ElementType type2 = item2->type();
1460 :
1461 4018857 : double padding = paddingTable.at(type1).at(type2);
1462 4018857 : double scaling = (item1->mag() + item2->mag()) / 2;
1463 :
1464 4018857 : if (type1 == ElementType::NOTE && isSpecialNotePaddingType(type2)) {
1465 595587 : computeNotePadding(toNote(item1), item2, padding, scaling);
1466 3423270 : } else if (type1 == ElementType::LYRICS && isSpecialLyricsPaddingType(type2)) {
1467 20742 : computeLyricsPadding(toLyrics(item1), item2, padding);
1468 3402528 : } else if (type2 == ElementType::CHORD_BRACKET) {
1469 0 : computeChordBracketPadding(item1, toChordBracket(item2), padding);
1470 : } else {
1471 3402528 : padding *= scaling;
1472 : }
1473 :
1474 4018857 : if (!item1->isLedgerLine() && item2->isRest()) {
1475 339595 : computeLedgerRestPadding(toRest(item2), padding);
1476 : }
1477 :
1478 4018857 : return padding;
1479 : }
1480 :
1481 972558 : bool HorizontalSpacing::isSpecialNotePaddingType(ElementType type)
1482 : {
1483 972558 : switch (type) {
1484 595587 : case ElementType::NOTE:
1485 : case ElementType::REST:
1486 : case ElementType::STEM:
1487 595587 : return true;
1488 376971 : default:
1489 376971 : return false;
1490 : }
1491 : }
1492 :
1493 595587 : void HorizontalSpacing::computeNotePadding(const Note* note, const EngravingItem* item2, double& padding, double scaling)
1494 : {
1495 595587 : const MStyle& style = note->style();
1496 :
1497 595587 : bool sameVoiceNoteOrStem = (item2->isNote() || item2->isStem()) && note->track() == item2->track();
1498 595587 : bool areAdjacentByDuration = (note->rtick() + note->chord()->actualTicks()) == item2->rtick();
1499 595587 : if (sameVoiceNoteOrStem || areAdjacentByDuration) {
1500 521548 : bool intersection = note->shape().translate(note->pos()).intersects(item2->shape().translate(item2->pos()));
1501 521548 : if (intersection) {
1502 339278 : padding = std::max(padding, static_cast<double>(style.styleAbsolute(Sid::minNoteDistance)));
1503 : }
1504 : }
1505 :
1506 595587 : padding *= scaling;
1507 :
1508 595587 : if (!(item2->isNote() || item2->isRest())) {
1509 216705 : return;
1510 : }
1511 :
1512 378882 : if (note->isGrace() && item2->isNote() && toNote(item2)->isGrace()) {
1513 : // Grace-to-grace
1514 3937 : padding = std::max(padding, static_cast<double>(style.styleAbsolute(Sid::graceToGraceNoteDist)));
1515 374945 : } else if (note->isGrace() && (item2->isRest() || (item2->isNote() && !toNote(item2)->isGrace()))) {
1516 : // Grace-to-main
1517 6601 : padding = std::max(padding, static_cast<double>(style.styleAbsolute(Sid::graceToMainNoteDist)));
1518 368344 : } else if (!note->isGrace() && item2->isNote() && toNote(item2)->isGrace()) {
1519 : // Main-to-grace
1520 3584 : padding = std::max(padding, static_cast<double>(style.styleAbsolute(Sid::graceToMainNoteDist)));
1521 : }
1522 :
1523 378882 : if (!note->fretString().empty() && item2->isNote()) { // This is a TAB fret mark
1524 : static constexpr double tabFretPaddingIncrease = 1.5; // TODO: style?
1525 3493 : padding *= tabFretPaddingIncrease;
1526 : }
1527 :
1528 378882 : if (!item2->isNote()) {
1529 54035 : return;
1530 : }
1531 :
1532 324847 : const Note* note2 = toNote(item2);
1533 324847 : if (note->lineAttachPoints().empty() || note2->lineAttachPoints().empty()) {
1534 307613 : return;
1535 : }
1536 :
1537 : // Allocate space for minTieLength, minGlissandoLength & minBendLength
1538 40726 : for (LineAttachPoint laPoint1 : note->lineAttachPoints()) {
1539 23492 : if (!laPoint1.line()->addToSkyline()) {
1540 28 : continue;
1541 : }
1542 55720 : for (LineAttachPoint laPoint2 : note2->lineAttachPoints()) {
1543 32256 : if (laPoint1.line() != laPoint2.line()) {
1544 25629 : continue;
1545 : }
1546 :
1547 6627 : double minEndPointsDistance = 0.0;
1548 6627 : if (laPoint1.line()->isTie()) {
1549 5394 : minEndPointsDistance = style.styleAbsolute(Sid::minTieLength);
1550 1233 : } else if (laPoint1.line()->isGlissando()) {
1551 518 : bool straight = toGlissando(laPoint1.line())->glissandoType() == GlissandoType::STRAIGHT;
1552 : double minGlissandoLength = straight
1553 518 : ? style.styleAbsolute(Sid::minStraightGlissandoLength)
1554 110 : : style.styleAbsolute(Sid::minWigglyGlissandoLength);
1555 518 : minEndPointsDistance = minGlissandoLength;
1556 715 : } else if (laPoint1.line()->isGuitarBend()) {
1557 706 : double minBendLength = 2 * note->spatium(); // TODO: style
1558 706 : minEndPointsDistance = minBendLength;
1559 9 : } else if (laPoint1.line()->isNoteLine()) {
1560 9 : minEndPointsDistance = style.styleAbsolute(Sid::minStraightGlissandoLength);
1561 : }
1562 :
1563 6627 : double lapPadding = (laPoint1.pos().x() - note->width()) + minEndPointsDistance - laPoint2.pos().x();
1564 6627 : lapPadding *= scaling;
1565 :
1566 6627 : padding = std::max(padding, lapPadding);
1567 : }
1568 : }
1569 : }
1570 :
1571 339595 : void HorizontalSpacing::computeLedgerRestPadding(const Rest* rest2, double& padding)
1572 : {
1573 339595 : SymId restSym = rest2->ldata()->sym();
1574 339595 : switch (restSym) {
1575 639 : case SymId::restWholeLegerLine:
1576 : case SymId::restDoubleWholeLegerLine:
1577 : case SymId::restHalfLegerLine:
1578 639 : padding += rest2->ldata()->bbox().left();
1579 639 : return;
1580 338956 : default:
1581 338956 : return;
1582 : }
1583 : }
1584 :
1585 38503 : bool HorizontalSpacing::isSpecialLyricsPaddingType(ElementType type)
1586 : {
1587 38503 : switch (type) {
1588 20742 : case ElementType::NOTE:
1589 : case ElementType::REST:
1590 : case ElementType::LYRICS:
1591 20742 : return true;
1592 17761 : default:
1593 17761 : return false;
1594 : }
1595 : }
1596 :
1597 20742 : void HorizontalSpacing::computeLyricsPadding(const Lyrics* lyrics1, const EngravingItem* item2, double& padding)
1598 : {
1599 20742 : const MStyle& style = lyrics1->style();
1600 :
1601 20742 : bool leaveSpaceForMelisma = lyrics1->separator() && lyrics1->separator()->isEndMelisma() && style.styleB(Sid::lyricsMelismaForce);
1602 20742 : if (leaveSpaceForMelisma) {
1603 0 : double spaceForMelisma = style.styleAbsolute(Sid::lyricsMelismaMinLength) + 2
1604 0 : * style.styleAbsolute(Sid::lyricsMelismaPad);
1605 0 : padding = std::max(padding, spaceForMelisma);
1606 0 : return;
1607 : }
1608 :
1609 20742 : if (item2->isLyrics()) {
1610 10722 : LyricsSyllabic syllabicType = lyrics1->syllabic();
1611 9385 : bool leaveSpaceForDash = (syllabicType == LyricsSyllabic::BEGIN || syllabicType == LyricsSyllabic::MIDDLE)
1612 20107 : && style.styleB(Sid::lyricsDashForce);
1613 10722 : if (leaveSpaceForDash) {
1614 1577 : double spaceForDash = style.styleAbsolute(Sid::lyricsDashMinLength) + 2 * style.styleAbsolute(Sid::lyricsDashPad);
1615 1577 : padding = std::max(padding, spaceForDash);
1616 : }
1617 : }
1618 : }
1619 :
1620 0 : void HorizontalSpacing::computeChordBracketPadding(const EngravingItem* item1, const ChordBracket* chordBracket, double& padding)
1621 : {
1622 0 : const Chord* chord = chordBracket->chord();
1623 0 : const Chord* itemChord = toChord(item1->findAncestor(ElementType::CHORD));
1624 0 : if (chord && itemChord && chord->segment() == itemChord->segment() && chord->part() == itemChord->part()) {
1625 : // Padding a right-handed chord bracket to chords in the same Segment and part: use the same padding values as the left-handed case
1626 0 : padding = item1->score()->paddingTables().horizontalPaddingTable().at(ElementType::CHORD_BRACKET).at(item1->type());
1627 : }
1628 0 : }
1629 :
1630 4090904 : KerningType HorizontalSpacing::computeKerning(const EngravingItem* item1, const EngravingItem* item2)
1631 : {
1632 4090904 : if (item1->isArticulationOrFermata() || item2->isArticulationOrFermata()) {
1633 63526 : return computeArticulationAndFermataKerning(item1, item2);
1634 : }
1635 :
1636 4027378 : if (ignoreItems(item1, item2)) {
1637 18 : return KerningType::ALLOW_COLLISION;
1638 : }
1639 :
1640 4027360 : if (isSameVoiceKerningLimited(item1) && isSameVoiceKerningLimited(item2) && item1->track() == item2->track()) {
1641 1044411 : return KerningType::NON_KERNING;
1642 : }
1643 :
1644 5018198 : if ((isNeverKernable(item1) || isNeverKernable(item2))
1645 5018198 : && !(isAlwaysKernable(item1) || isAlwaysKernable(item2))) {
1646 1294437 : return KerningType::NON_KERNING;
1647 : }
1648 :
1649 1688512 : return doComputeKerningType(item1, item2);
1650 : }
1651 :
1652 4090930 : double HorizontalSpacing::computeVerticalClearance(const EngravingItem* item1, const EngravingItem* item2, double spatium)
1653 : {
1654 : // To be possibly expanded to more cases
1655 4090930 : if (item2 && item2->isAccidental()) {
1656 518766 : return 0.1 * spatium;
1657 3572164 : } else if ((item1 && item1->isParenthesis()) || (item2 && item2->isParenthesis())) {
1658 68243 : return 0.1 * spatium;
1659 : }
1660 :
1661 3503921 : return 0.2 * spatium;
1662 : }
1663 :
1664 5934943 : bool HorizontalSpacing::isSameVoiceKerningLimited(const EngravingItem* item)
1665 : {
1666 5934943 : ElementType type = item->type();
1667 :
1668 5934943 : switch (type) {
1669 3121634 : case ElementType::NOTE:
1670 : case ElementType::NOTEDOT:
1671 : case ElementType::REST:
1672 : case ElementType::STEM:
1673 : case ElementType::CHORDLINE:
1674 : case ElementType::BREATH:
1675 3121634 : return true;
1676 2813309 : default:
1677 2813309 : return false;
1678 : }
1679 : }
1680 :
1681 5018198 : bool HorizontalSpacing::isNeverKernable(const EngravingItem* item)
1682 : {
1683 5018198 : ElementType type = item->type();
1684 :
1685 5018198 : switch (type) {
1686 146252 : case ElementType::CLEF:
1687 146252 : if (toClef(item)->isMidMeasureClef()) {
1688 29074 : return false;
1689 : }
1690 : // fall through
1691 : case ElementType::KEYSIG:
1692 : case ElementType::BAR_LINE:
1693 1210962 : return true;
1694 136640 : case ElementType::TIMESIG:
1695 136640 : return !toTimeSig(item)->isAboveStaves();
1696 3641522 : default:
1697 3641522 : return false;
1698 : }
1699 : }
1700 :
1701 2678421 : bool HorizontalSpacing::isAlwaysKernable(const EngravingItem* item)
1702 : {
1703 2678421 : return item->isTextBase() || item->isChordLine() || item->isParenthesis() || item->isFretDiagram();
1704 : }
1705 :
1706 4027378 : bool HorizontalSpacing::ignoreItems(const EngravingItem* item1, const EngravingItem* item2)
1707 : {
1708 4027378 : if (item1->isRest() && toRest(item1)->isFullMeasureRest()) {
1709 : // Full-measure rest must ignore cross-stave notes
1710 144122 : return item1->staffIdx() != item2->staffIdx();
1711 : }
1712 :
1713 3883256 : return false;
1714 : }
1715 :
1716 1688512 : KerningType HorizontalSpacing::doComputeKerningType(const EngravingItem* item1, const EngravingItem* item2)
1717 : {
1718 1688512 : ElementType type1 = item1->type();
1719 1688512 : switch (type1) {
1720 14123 : case ElementType::BAR_LINE:
1721 14123 : return item2->isLyrics() || item2->isHarmony() || item2->isFretDiagram() ? KerningType::ALLOW_COLLISION : KerningType::NON_KERNING;
1722 288 : case ElementType::CHORDLINE:
1723 288 : return item2->isBarLine() ? KerningType::ALLOW_COLLISION : KerningType::KERNING;
1724 53806 : case ElementType::HARMONY:
1725 : case ElementType::FRET_DIAGRAM:
1726 53806 : return item2->isHarmony() || item2->isFretDiagram() ? KerningType::NON_KERNING : KerningType::KERNING;
1727 38464 : case ElementType::LYRICS
1728 38464 : : return computeLyricsKerningType(toLyrics(item1), item2);
1729 348543 : case ElementType::NOTE:
1730 348543 : return computeNoteKerningType(toNote(item1), item2);
1731 17852 : case ElementType::CLEF:
1732 17852 : return item2->isNote() ? KerningType::KERN_UNTIL_RIGHT_EDGE : KerningType::KERNING;
1733 5453 : case ElementType::STEM_SLASH:
1734 5453 : return computeStemSlashKerningType(toStemSlash(item1), item2);
1735 41016 : case ElementType::PARENTHESIS:
1736 41016 : return item2->isBarLine() ? KerningType::NON_KERNING : KerningType::KERNING;
1737 146 : case ElementType::GUITAR_BEND_SEGMENT:
1738 146 : return item2->isNote() || item2->isRest() ? KerningType::NON_KERNING : KerningType::KERNING;
1739 1168821 : default:
1740 1168821 : return KerningType::KERNING;
1741 : }
1742 : }
1743 :
1744 348543 : KerningType HorizontalSpacing::computeNoteKerningType(const Note* note, const EngravingItem* item2)
1745 : {
1746 348543 : if (item2->isClef()) {
1747 1767 : return KerningType::KERN_UNTIL_RIGHT_EDGE;
1748 : }
1749 :
1750 346776 : EngravingObject* nextParent = item2->ownershipParent();
1751 346776 : if (nextParent && nextParent->isNote() && toNote(nextParent)->isTrillCueNote()) {
1752 380 : return KerningType::NON_KERNING;
1753 : }
1754 :
1755 346396 : Chord* c = note->chord();
1756 346396 : if (!c) {
1757 0 : return KerningType::KERNING;
1758 : }
1759 346396 : if (item2->isLyrics() && c->isMelismaEnd()) {
1760 317 : Note* melismaEndNote = c->up() ? c->downNote() : c->upNote();
1761 317 : return note == melismaEndNote ? KerningType::NON_KERNING : KerningType::KERNING;
1762 : }
1763 346079 : if (c->allowKerningAbove() && c->allowKerningBelow()) {
1764 337450 : return KerningType::KERNING;
1765 : }
1766 :
1767 8629 : if (c->up() && note->ldata()->pos().x() > 0) {
1768 : // Offset seconds can always be kerned into
1769 224 : return KerningType::KERNING;
1770 : }
1771 :
1772 8405 : bool kerningAbove = item2->canvasPos().y() < note->canvasPos().y();
1773 8405 : if (kerningAbove && !c->allowKerningAbove()) {
1774 2116 : return KerningType::NON_KERNING;
1775 : }
1776 6289 : if (!kerningAbove && !c->allowKerningBelow()) {
1777 2272 : return KerningType::NON_KERNING;
1778 : }
1779 :
1780 4017 : return KerningType::KERNING;
1781 : }
1782 :
1783 5453 : KerningType HorizontalSpacing::computeStemSlashKerningType(const StemSlash* stemSlash, const EngravingItem* item2)
1784 : {
1785 5453 : if (!stemSlash->chord() || !stemSlash->chord()->beam() || !item2->ownershipParent()) {
1786 4818 : return KerningType::KERNING;
1787 : }
1788 :
1789 635 : EngravingObject* nextParent = item2->ownershipParent();
1790 635 : Chord* nextChord = nullptr;
1791 635 : if (nextParent->isChord()) {
1792 616 : nextChord = toChord(nextParent);
1793 19 : } else if (nextParent->isNote()) {
1794 4 : nextChord = toChord(nextParent->ownershipParent());
1795 : }
1796 635 : if (!nextChord) {
1797 15 : return KerningType::KERNING;
1798 : }
1799 :
1800 620 : if (nextChord->beam() && nextChord->beam() == stemSlash->chord()->beam()) {
1801 : // Stem slash is allowed to collide with items from the same grace notes group
1802 106 : return KerningType::ALLOW_COLLISION;
1803 : }
1804 :
1805 514 : return KerningType::KERNING;
1806 : }
1807 :
1808 38464 : KerningType HorizontalSpacing::computeLyricsKerningType(const Lyrics* lyrics1, const EngravingItem* item2)
1809 : {
1810 38464 : if (item2->isLyrics()) {
1811 10722 : const Lyrics* lyrics2 = toLyrics(item2);
1812 10722 : if (lyrics1->verse() == lyrics2->verse()) {
1813 8029 : return KerningType::NON_KERNING;
1814 : }
1815 : }
1816 :
1817 30435 : if ((item2->isNote() || item2->isRest()) && lyrics1->style().styleB(Sid::lyricsMelismaForce)) {
1818 0 : LyricsLine* melismaLine = lyrics1->separator();
1819 0 : if (melismaLine && melismaLine->isEndMelisma() && item2->tick() >= melismaLine->tick2()) {
1820 0 : return KerningType::NON_KERNING;
1821 : }
1822 : }
1823 :
1824 30435 : return KerningType::ALLOW_COLLISION;
1825 : }
1826 :
1827 63526 : KerningType HorizontalSpacing::computeArticulationAndFermataKerning(const EngravingItem* item1, const EngravingItem* item2)
1828 : {
1829 63526 : if (item1->isArticulationOrFermata()) {
1830 35873 : if (item2->isArticulationOrFermata()) {
1831 7584 : bool firstAbove = item1->isArticulationFamily() ? toArticulation(item1)->up() : item1->placeAbove();
1832 7584 : bool secondAbove = item2->isArticulationFamily() ? toArticulation(item2)->up() : item2->placeAbove();
1833 7584 : if (firstAbove == secondAbove) {
1834 5546 : return KerningType::NON_KERNING;
1835 : }
1836 : }
1837 : }
1838 :
1839 57980 : return KerningType::KERNING;
1840 : }
1841 :
1842 269004 : void HorizontalSpacing::computeHangingLineWidth(const Segment* firstSeg, const Segment* nextSeg, double& width, bool systemHeaderGap,
1843 : bool systemEnd)
1844 : {
1845 269004 : const MStyle& style = firstSeg->style();
1846 269004 : const Segment* crSeg = firstSeg->isChordRestType() ? firstSeg : nextSeg;
1847 269004 : const Segment* otherSeg = firstSeg->isChordRestType() ? nextSeg : firstSeg;
1848 269004 : const bool incoming = !firstSeg->isChordRestType();
1849 269004 : const Measure* crMeasure = crSeg->measure();
1850 269004 : const size_t ntracks = crSeg->score()->ntracks();
1851 :
1852 269004 : std::vector<double> tieLengths;
1853 :
1854 1853364 : for (track_idx_t track = 0; track < ntracks; track++) {
1855 : // Hanging lines only occur at start or end of a measure
1856 1584360 : const ChordRest* cr = incoming ? crMeasure->firstChordRest(track) : crMeasure->lastChordRest(track);
1857 1584360 : if (!cr || !cr->isChord() || cr->segment() != crSeg) {
1858 1478110 : continue;
1859 : }
1860 :
1861 106250 : const double headerLineMargin = systemHeaderGap ? otherSeg->style().styleAbsolute(Sid::headerToLineStartDistance)
1862 84046 : : otherSeg->style().styleAbsolute(Sid::repeatBarlineDotSeparation);
1863 106250 : const double endSystemMargin = style.styleAbsolute(Sid::lineEndToBarlineDistance);
1864 :
1865 238435 : for (const Note* note : toChord(cr)->notes()) {
1866 132185 : const bool lineBack = note->spannerBack().size()
1867 132185 : || (note->tieBack() && !note->tieBack()->segmentsEmpty());
1868 132185 : const bool lineFor = note->spannerFor().size()
1869 132185 : || (note->tieFor() && !note->tieFor()->segmentsEmpty());
1870 132185 : if (!(lineBack || lineFor) || note->lineAttachPoints().empty()) {
1871 124486 : continue;
1872 : }
1873 :
1874 16394 : for (const LineAttachPoint& lap : note->lineAttachPoints()) {
1875 8695 : if (lap.start() == incoming) {
1876 8478 : continue;
1877 : }
1878 4136 : const Spanner* attachedLine = toSpanner(lap.line());
1879 4136 : if (!attachedLine->addToSkyline()) {
1880 62 : continue;
1881 : }
1882 :
1883 : // Partial ties are adjusted wherever they are in the system, other spanners are only adjusted over system breaks
1884 4074 : if (!(systemEnd || systemHeaderGap) && !attachedLine->isPartialTie()) {
1885 2426 : continue;
1886 : }
1887 :
1888 1648 : double minLength = 0.0;
1889 1648 : if (attachedLine->isGlissando()) {
1890 0 : bool straight = toGlissando(attachedLine)->glissandoType() == GlissandoType::STRAIGHT;
1891 0 : minLength = straight ? style.styleAbsolute(Sid::minStraightGlissandoLength)
1892 0 : : style.styleAbsolute(Sid::minWigglyGlissandoLength);
1893 1648 : } else if (attachedLine->isNoteLine()) {
1894 0 : minLength = style.styleAbsolute(Sid::minStraightGlissandoLength);
1895 : }
1896 :
1897 1648 : const double notePosX = note->pos().x() + toChord(cr)->pos().x();
1898 1648 : const double lineNoteEndPos = (incoming ? width : 0.0) + notePosX + lap.pos().x();
1899 : const double lineSegEndPos
1900 1648 : = (incoming ? otherSeg->minRight() + headerLineMargin : width + otherSeg->minLeft() - endSystemMargin);
1901 :
1902 1648 : const double lineStartPointX = incoming ? lineSegEndPos : lineNoteEndPos;
1903 1648 : const double lineEndPointX = incoming ? lineNoteEndPos : lineSegEndPos;
1904 1648 : const double lineLength = lineEndPointX - lineStartPointX;
1905 :
1906 1648 : if (attachedLine->isTie()) {
1907 1431 : tieLengths.push_back(lineLength);
1908 1431 : continue;
1909 : }
1910 :
1911 217 : if (lineLength < minLength) {
1912 0 : width += minLength - lineLength;
1913 : }
1914 : }
1915 : }
1916 : }
1917 :
1918 269004 : if (tieLengths.empty()) {
1919 267952 : return;
1920 : }
1921 :
1922 1052 : const double maxLength = *std::max_element(tieLengths.begin(), tieLengths.end());
1923 1052 : const double oldWidth = width;
1924 2483 : for (double& tieLength : tieLengths) {
1925 : // Adjust for new width
1926 1431 : tieLength += width - oldWidth;
1927 : const double minLength
1928 1431 : = muse::RealIsEqual(tieLength, maxLength) ? style.styleAbsolute(Sid::minHangingTieLength) : style.styleAbsolute(
1929 1431 : Sid::minTieLength);
1930 :
1931 1431 : if (tieLength < minLength) {
1932 163 : width += minLength - tieLength;
1933 : }
1934 : }
1935 269004 : }
|