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 "pagelayout.h"
23 :
24 : #include "realfn.h"
25 :
26 : #include "dom/barline.h"
27 : #include "dom/beam.h"
28 : #include "dom/box.h"
29 : #include "dom/bracketitem.h"
30 : #include "dom/chord.h"
31 : #include "dom/chordrest.h"
32 : #include "dom/durationelement.h"
33 : #include "dom/factory.h"
34 : #include "dom/fingering.h"
35 : #include "dom/measure.h"
36 : #include "dom/measurebase.h"
37 : #include "dom/note.h"
38 : #include "dom/page.h"
39 : #include "dom/score.h"
40 : #include "dom/segment.h"
41 : #include "dom/slur.h"
42 : #include "dom/spacer.h"
43 : #include "dom/spannermap.h"
44 : #include "dom/staff.h"
45 : #include "dom/staffvisibilityindicator.h"
46 : #include "dom/system.h"
47 : #include "dom/tremolosinglechord.h"
48 : #include "dom/tremolotwochord.h"
49 : #include "dom/tuplet.h"
50 :
51 : #include "arpeggiolayout.h"
52 : #include "beamlayout.h"
53 : #include "chordlayout.h"
54 : #include "chordbracketlayout.h"
55 : #include "headerfooterlayout.h"
56 : #include "masklayout.h"
57 : #include "measurelayout.h"
58 : #include "slurtielayout.h"
59 : #include "systemlayout.h"
60 : #include "tlayout.h"
61 : #include "tupletlayout.h"
62 : #include "verticalgapdata.h"
63 : #include "systemheaderlayout.h"
64 :
65 : #include "log.h"
66 :
67 : using namespace mu::engraving;
68 : using namespace mu::engraving::rendering::score;
69 :
70 : //---------------------------------------------------------
71 : // getNextPage
72 : //---------------------------------------------------------
73 :
74 4161 : void PageLayout::getNextPage(LayoutContext& ctx)
75 : {
76 4161 : LayoutState& state = ctx.mutState();
77 4161 : DomAccessor& dom = ctx.mutDom();
78 :
79 4161 : if (!state.page() || state.pageIdx() >= dom.npages()) {
80 3554 : state.setPage(Factory::createPage(dom.score()));
81 3554 : dom.pages().push_back(state.page());
82 3554 : state.setPrevSystem(nullptr);
83 3554 : state.setPageOldMeasure(nullptr);
84 : } else {
85 607 : state.setPage(dom.pages()[state.pageIdx()]);
86 607 : std::vector<System*>& systems = state.page()->systems();
87 607 : state.setPageOldMeasure(systems.empty() || systems.back()->measures().empty() ? nullptr : systems.back()->measures().back());
88 607 : const system_idx_t i = muse::indexOf(systems, state.curSystem());
89 607 : if ((i < systems.size()) && i > 0 && systems[i - 1]->page() == state.page()) {
90 : // Current and previous systems are on the current page.
91 : // Erase only the current and the following systems
92 : // as the previous one will not participate in layout.
93 115 : systems.erase(systems.begin() + i, systems.end());
94 : } else { // system is not on the current page (or will be the first one)
95 492 : systems.clear();
96 : }
97 607 : state.setPrevSystem(systems.empty() ? nullptr : systems.back());
98 : }
99 4161 : state.page()->mutldata()->setBbox(0.0, 0.0, ctx.conf().loWidth(), ctx.conf().loHeight());
100 4161 : state.page()->setPageNumber(state.pageIdx());
101 4161 : double x = 0.0;
102 4161 : double y = 0.0;
103 4161 : if (state.pageIdx()) {
104 161 : const Page* prevPage = dom.pages().at(state.pageIdx() - 1);
105 161 : if (MScore::verticalOrientation()) {
106 0 : y = prevPage->pos().y() + state.page()->height() + MScore::verticalPageGap;
107 : } else {
108 161 : double gap = (state.pageIdx() + ctx.conf().pageNumberOffset())
109 161 : & 1 ? MScore::horizontalPageGapOdd : MScore::horizontalPageGapEven;
110 161 : x = prevPage->pos().x() + state.page()->width() + gap;
111 : }
112 : }
113 4161 : state.setPageIdx(state.pageIdx() + 1);
114 4161 : state.page()->setPos(x, y);
115 4161 : }
116 :
117 : //---------------------------------------------------------
118 : // collectPage
119 : //---------------------------------------------------------
120 :
121 4125 : void PageLayout::collectPage(LayoutContext& ctx)
122 : {
123 4140 : TRACEFUNC;
124 :
125 4125 : Page* page = ctx.mutState().page();
126 4125 : const LayoutConfiguration& conf = ctx.conf();
127 :
128 : LAYOUT_CALL() << "pageNumber: " << page->pageNumber();
129 :
130 : /* The page count will be wrong unless this is the final page, but we need to
131 : * take into account the space taken up by headers/footers, when positioning
132 : * other elements. If there was a page count involved, all headers/footers will
133 : * be re-computed after all pages are laid out */
134 4125 : HeaderFooterLayout::layoutHeaderFooter(ctx, page);
135 :
136 4125 : const double slb = conf.styleAbsolute(Sid::staffLowerBorder);
137 4125 : const bool breakPages = conf.viewMode() != LayoutMode::SYSTEM;
138 4125 : const double footerExtension = HeaderFooterLayout::footerExtension(ctx, page);
139 4125 : const double headerExtension = HeaderFooterLayout::headerExtension(ctx, page);
140 4125 : const double headerFooterPadding = conf.styleAbsolute(Sid::staffHeaderFooterPadding);
141 4125 : const double endY = page->height() - page->bm();
142 4125 : double y = 0.0;
143 :
144 4125 : System* nextSystem = nullptr;
145 4125 : int systemIdx = -1;
146 :
147 4125 : auto getPageLock = [&ctx](MeasureBase* mb) -> const RangeLock* {
148 4125 : return mb && ctx.conf().viewMode() == LayoutMode::PAGE
149 8250 : ? ctx.dom().pageLocks()->lockStartingAt(mb) : nullptr;
150 4125 : };
151 :
152 4125 : const RangeLock* pageLock = nullptr;
153 :
154 : // re-calculate positions for systems before current
155 : // (they may have been filled on previous layout)
156 4125 : size_t pSystems = page->systems().size();
157 4125 : if (pSystems > 0) {
158 115 : SystemLayout::restoreLayout2(page->system(0), ctx);
159 115 : y = page->system(0)->y() + page->system(0)->height();
160 :
161 : // Get existing page lock
162 115 : pageLock = getPageLock(page->system(0)->first());
163 : } else {
164 4010 : y = page->tm();
165 : }
166 :
167 4152 : for (size_t i = 1; i < pSystems; ++i) {
168 27 : System* cs = page->system(i);
169 27 : System* ps = page->system(i - 1);
170 27 : double distance = SystemLayout::minDistance(ps, cs, ctx);
171 27 : y += distance;
172 27 : cs->setPos(page->lm(), y);
173 27 : SystemLayout::restoreLayout2(cs, ctx);
174 27 : y += cs->height();
175 : }
176 :
177 : for (;;) {
178 : //
179 : // calculate distance to previous system
180 : //
181 : double distance;
182 10053 : if (ctx.state().prevSystem()) {
183 6043 : distance = SystemLayout::minDistance(ctx.state().prevSystem(), ctx.state().curSystem(), ctx);
184 : } else {
185 : // this is the first system on page
186 : // get page lock here
187 4010 : pageLock = getPageLock(ctx.state().curSystem()->first());
188 4010 : if (ctx.state().curSystem()->vbox()) {
189 : // if the header exists and there is a frame, move the frame downwards
190 : // to avoid collisions
191 2149 : distance = headerExtension ? headerExtension + headerFooterPadding : 0.0;
192 : } else {
193 1861 : distance = ctx.conf().styleAbsolute(Sid::staffUpperBorder);
194 1861 : bool fixedDistance = false;
195 7527 : for (MeasureBase* mb : ctx.mutState().curSystem()->measures()) {
196 5666 : if (mb->isMeasure()) {
197 5655 : Measure* m = toMeasure(mb);
198 5655 : Spacer* sp = m->vspacerUp(0);
199 5655 : if (sp) {
200 0 : if (sp->spacerType() == SpacerType::FIXED) {
201 0 : distance = sp->absoluteGap();
202 0 : fixedDistance = true;
203 0 : break;
204 : } else {
205 0 : distance = std::max(distance, sp->absoluteGap());
206 : }
207 : }
208 : }
209 : }
210 1861 : if (!fixedDistance) {
211 1861 : double top = ctx.state().curSystem()->minTop();
212 : // ensure it doesn't collide with header
213 1861 : if (headerExtension > 0.0) {
214 134 : top += headerExtension + headerFooterPadding;
215 : }
216 1861 : distance = std::max(distance, top);
217 : }
218 : }
219 : }
220 :
221 10053 : y += distance;
222 10053 : ctx.mutState().curSystem()->setPos(ctx.state().page()->lm(), y);
223 10053 : SystemLayout::restoreLayout2(ctx.mutState().curSystem(), ctx);
224 10053 : ctx.mutState().page()->appendSystem(ctx.mutState().curSystem());
225 10053 : y += ctx.state().curSystem()->height();
226 :
227 : //
228 : // check for page break or if next system will fit on page
229 : //
230 10053 : bool collected = false;
231 10053 : if (ctx.state().rangeDone()) {
232 : // take next system unchanged
233 859 : if (systemIdx > 0) {
234 0 : nextSystem = muse::value(ctx.mutDom().systems(), systemIdx++);
235 0 : if (!nextSystem) {
236 : // TODO: handle next movement
237 : }
238 : } else {
239 859 : nextSystem = ctx.state().systemList().empty() ? 0 : muse::takeFirst(ctx.mutState().systemList());
240 859 : if (nextSystem) {
241 590 : ctx.mutDom().systems().push_back(nextSystem);
242 : }
243 : }
244 : } else {
245 9194 : nextSystem = SystemLayout::collectSystem(ctx);
246 9194 : if (nextSystem) {
247 5509 : collected = true;
248 : }
249 : }
250 :
251 10053 : if (ctx.dom().lastSegment()) {
252 10051 : float curPercent = static_cast<float>(ctx.state().measureNumber()) / ctx.dom().lastSegment()->measure()->index();
253 10051 : page->score()->layoutProgressChannel().send(curPercent);
254 : }
255 :
256 10053 : ctx.mutState().setPrevSystem(ctx.mutState().curSystem());
257 10053 : assert(ctx.state().curSystem() != nextSystem);
258 10053 : ctx.mutState().setCurSystem(nextSystem);
259 :
260 10053 : MeasureBase* endMB = ctx.state().prevSystem()->last();
261 10053 : bool endOfPageLock = endMB && endMB->isEndOfPageLock();
262 10053 : const MeasureBase* nextMB = endMB ? endMB->nextMM() : nullptr;
263 10053 : bool pageLockStart = nextMB && nextMB->isStartOfPageLock();
264 :
265 10053 : bool isPageBreak = !ctx.state().curSystem()
266 10053 : || (breakPages && (ctx.state().prevSystem()->pageBreak() || endOfPageLock || pageLockStart));
267 :
268 10053 : if (!isPageBreak && !pageLock) {
269 5996 : double dist = SystemLayout::minDistance(ctx.state().prevSystem(), ctx.state().curSystem(), ctx)
270 5996 : + ctx.state().curSystem()->height();
271 5996 : Box* vbox = ctx.state().curSystem()->vbox();
272 5996 : if (vbox) {
273 137 : if (footerExtension > 0) {
274 8 : dist += footerExtension;
275 : }
276 5859 : } else if (!ctx.state().prevSystem()->hasFixedDownDistance()) {
277 : // Down spacers define distance to the following system, not to the page footer.
278 : // Using them here can visually pin/stretch the current system to page edge.
279 5857 : double margin = ctx.state().curSystem()->minBottom();
280 : // ensure it doesn't collide with footer
281 5857 : if (footerExtension > 0) {
282 54 : margin += footerExtension + headerFooterPadding;
283 : }
284 5857 : dist += std::max(margin, slb);
285 : }
286 5996 : isPageBreak = (y + dist) >= endY && breakPages;
287 : }
288 10053 : if (isPageBreak) {
289 4125 : double dist = ctx.state().prevSystem()->minBottom();
290 4125 : if (!ctx.conf().isVerticalSpreadEnabled()) {
291 1015 : dist = std::max(dist, ctx.state().prevSystem()->spacerDistance(false));
292 : }
293 4125 : double footerPadding = 0.0;
294 : // ensure it doesn't collide with footer
295 4125 : if (footerExtension > 0) {
296 41 : footerPadding = footerExtension + headerFooterPadding;
297 41 : dist += footerPadding;
298 : }
299 4125 : dist = std::max(dist, slb);
300 4125 : layoutPage(ctx, page, endY - (y + dist), footerPadding, pageLock);
301 : // if we collected a system we cannot fit onto this page,
302 : // we need to collect next page in order to correctly set system positions
303 4125 : if (collected) {
304 160 : ctx.mutState().setPageOldMeasure(nullptr);
305 : }
306 4125 : break;
307 : }
308 5928 : }
309 :
310 : // All staves have been hidden, so correct last system's header
311 4125 : System* lastSys = ctx.mutState().page()->systems().back();
312 4125 : Measure* lastSysMeasure = lastSys->firstMeasure();
313 4125 : if (lastSysMeasure) {
314 4093 : MeasureLayout::addSystemHeader(lastSysMeasure, false, ctx);
315 : }
316 :
317 4125 : Fraction stick2 = Fraction(-1, 1);
318 14320 : for (System* s : page->systems()) {
319 46217 : for (MeasureBase* mb : s->measures()) {
320 36022 : if (!mb->isMeasure()) {
321 2519 : continue;
322 : }
323 33503 : Measure* m = toMeasure(mb);
324 33503 : if (stick2 == Fraction(-1, 1)) {
325 4122 : stick2 = m->tick();
326 : }
327 :
328 228431 : for (size_t track = 0; track < ctx.dom().ntracks(); ++track) {
329 920552 : for (Segment* segment = m->first(); segment; segment = segment->next()) {
330 725624 : EngravingItem* e2 = segment->element(track);
331 725624 : if (!e2) {
332 545598 : continue;
333 : }
334 180026 : if (e2->isChordRest()) {
335 92940 : if (!ctx.dom().staff(track2staff(track))->show()) {
336 800 : continue;
337 : }
338 92140 : ChordRest* cr = toChordRest(e2);
339 92140 : if (BeamLayout::isStartOfCrossBeam(cr)) { // layout cross staff beams
340 10 : TLayout::layoutBeam(cr->beam(), ctx);
341 10 : BeamLayout::checkCrossPosAndStemConsistency(cr->beam(), ctx);
342 : }
343 92140 : if (TupletLayout::notTopTuplet(cr)) {
344 : // fix layout of tuplets
345 41 : DurationElement* de = cr;
346 82 : while (de->tuplet() && de->tuplet()->elements().front() == de) {
347 41 : Tuplet* t = de->tuplet();
348 41 : TLayout::layoutTuplet(t, ctx);
349 41 : de = t;
350 : }
351 : }
352 :
353 92140 : if (cr->isChord()) {
354 48512 : Chord* c = toChord(cr);
355 49421 : for (Chord* cc : c->graceNotes()) {
356 909 : if (cc->beam() && cc->beam()->elements().front() == cc) {
357 92 : TLayout::layoutBeam(cc->beam(), ctx);
358 92 : BeamLayout::checkCrossPosAndStemConsistency(cc->beam(), ctx);
359 : }
360 909 : ChordLayout::layoutSpanners(cc, ctx);
361 909 : for (EngravingItem* element : cc->el()) {
362 0 : if (element->isSlur()) {
363 0 : TLayout::layoutItem(element, ctx);
364 : }
365 : }
366 : }
367 48512 : ArpeggioLayout::layoutArpeggio2(c->arpeggio(), ctx);
368 48680 : for (EngravingItem* e : c->el()) {
369 168 : if (e->isChordBracket()) {
370 0 : ChordBracket* bracket = toChordBracket(e);
371 0 : if (c->onTabStaff()) {
372 0 : TLayout::layoutItem(bracket, ctx);
373 : } else {
374 0 : ChordBracketLayout::updateVerticalGeometry(bracket, ctx);
375 : }
376 : }
377 : }
378 48512 : ChordLayout::layoutSpanners(c, ctx);
379 48512 : if (c->tremoloTwoChord()) {
380 220 : TremoloTwoChord* t = c->tremoloTwoChord();
381 220 : Chord* c1 = t->chord1();
382 220 : Chord* c2 = t->chord2();
383 220 : if (c1 && c2 && (c1->staffMove() || c2->staffMove())) {
384 : // For cross-staff tremolo, vertical justification may have changed
385 : // staff distances, and therefore also stem directions, so re-compute them
386 0 : ChordLayout::computeUp(c1, ctx); // This will also call a tremolo layout
387 0 : ChordLayout::computeUp(c2, ctx);
388 : }
389 : }
390 : }
391 87086 : } else if (e2->isBarLine()) {
392 55574 : TLayout::layoutBarLine2(toBarLine(e2), ctx);
393 : }
394 : }
395 : }
396 33503 : MeasureLayout::layout2(m, ctx);
397 : }
398 10195 : SystemLayout::layoutSystemLockIndicators(s, ctx);
399 10195 : if (ctx.conf().isMode(LayoutMode::PAGE)) {
400 10195 : SystemLayout::layoutPageLockIndicators(s);
401 : }
402 :
403 10195 : if (StaffVisibilityIndicator* visibilityIndicator = s->staffVisibilityIndicator()) {
404 48 : TLayout::layoutIndicatorIcon(visibilityIndicator, visibilityIndicator->mutldata());
405 : }
406 : }
407 :
408 : // If this is the last page we layout, we must also relayout the first barlines of the
409 : // next page, because they may have been altered while collecting the systems.
410 4125 : MeasureBase* lastOfThisPage = ctx.mutState().page()->systems().back()->measures().back();
411 4125 : MeasureBase* firstOfNextPage = lastOfThisPage ? lastOfThisPage->next() : nullptr;
412 4125 : if (firstOfNextPage && firstOfNextPage->isMeasure() && firstOfNextPage->tick() > ctx.state().endTick()) {
413 109 : for (Segment& segment : toMeasure(firstOfNextPage)->segments()) {
414 90 : if (!segment.isType(SegmentType::BarLineTypes)) {
415 57 : continue;
416 : }
417 277 : for (EngravingItem* item : segment.elist()) {
418 244 : if (item && item->isBarLine()) {
419 61 : TLayout::layoutBarLine2(toBarLine(item), ctx);
420 : }
421 : }
422 : }
423 : }
424 :
425 4125 : if (ctx.conf().isMode(LayoutMode::SYSTEM)) {
426 0 : const System* s = page->systems().back();
427 0 : double height = s ? s->pos().y() + s->height() + s->minBottom() : ctx.state().page()->tm();
428 0 : page->mutldata()->setBbox(0.0, 0.0, ctx.conf().loWidth(), height + ctx.state().page()->bm());
429 : }
430 :
431 4125 : layoutCrossStaffElements(ctx, page);
432 :
433 14320 : for (System* system : page->systems()) {
434 10195 : SystemLayout::centerElementsBetweenStaves(system);
435 10195 : ctx.mutState().removeSystemNeedingCentering(system);
436 : }
437 :
438 4125 : if (ctx.conf().styleV(Sid::timeSigPlacement).value<TimeSigPlacement>() == TimeSigPlacement::ACROSS_STAVES
439 4125 : && ctx.conf().styleB(Sid::timeSigCenterAcrossStaveGroup)) {
440 0 : for (const System* system : page->systems()) {
441 0 : SystemLayout::centerBigTimeSigsAcrossStaves(system);
442 : }
443 : }
444 :
445 4125 : MaskLayout::computeMasks(ctx, page);
446 :
447 4125 : layoutSystemDividers(ctx, page);
448 :
449 4125 : page->invalidateBspTree();
450 4125 : }
451 :
452 4125 : void PageLayout::layoutCrossStaffElements(LayoutContext& ctx, Page* page)
453 : {
454 14320 : for (System* system : page->systems()) {
455 10195 : if (!system->firstMeasure()) {
456 3010 : continue;
457 : }
458 :
459 7816 : Fraction stick = system->firstMeasure()->tick();
460 7816 : Fraction etick = system->endTick();
461 :
462 7816 : IF_ASSERT_FAILED(stick < etick) {
463 0 : continue;
464 : }
465 :
466 7816 : if (etick <= ctx.state().startTick() || etick > ctx.state().tick()) {
467 631 : continue;
468 : }
469 :
470 7185 : layoutCrossStaffSlurs(ctx, system);
471 :
472 7185 : layoutArticAndFingeringOnCrossStaffBeams(ctx, system);
473 : }
474 4125 : }
475 :
476 7185 : void PageLayout::layoutCrossStaffSlurs(LayoutContext& ctx, System* system)
477 : {
478 7185 : Fraction stick = system->firstMeasure()->tick();
479 7185 : Fraction etick = system->endTick();
480 :
481 7185 : auto spanners = ctx.dom().spannerMap().findOverlapping(stick.ticks(), etick.ticks());
482 9574 : for (auto interval : spanners) {
483 2389 : Spanner* sp = interval.value;
484 2389 : if (!sp->isSlur() || sp->tick() == system->endTick()) {
485 1489 : continue;
486 : }
487 900 : Slur* slur = toSlur(sp);
488 900 : if (slur->isCrossStaff() || slur->hasCrossBeams()) {
489 24 : SlurTieLayout::layoutSystem(slur, system, ctx);
490 :
491 24 : ChordRest* scr = toChordRest(slur->startElement());
492 24 : ChordRest* ecr = toChordRest(slur->endElement());
493 24 : if (scr && scr->isChord()) {
494 24 : ChordLayout::layoutArticulations3(toChord(scr), slur, ctx);
495 : }
496 24 : if (ecr && ecr->isChord()) {
497 24 : ChordLayout::layoutArticulations3(toChord(ecr), slur, ctx);
498 : }
499 : }
500 : }
501 7185 : }
502 :
503 7185 : void PageLayout::layoutArticAndFingeringOnCrossStaffBeams(LayoutContext& ctx, System* system)
504 : {
505 37697 : for (const MeasureBase* mb : system->measures()) {
506 30512 : if (!mb->isMeasure()) {
507 135 : continue;
508 : }
509 146924 : for (const Segment& segment : toMeasure(mb)->segments()) {
510 116547 : if (!segment.isChordRestType()) {
511 53337 : continue;
512 : }
513 417238 : for (EngravingItem* item : segment.elist()) {
514 354028 : if (!item || !item->isChord()) {
515 305774 : continue;
516 : }
517 :
518 48254 : Chord* c = toChord(item);
519 48254 : bool hasCrossBeam = c->beam() && (c->beam()->cross() || c->staffMove() != 0);
520 48254 : if (!hasCrossBeam) {
521 48037 : continue;
522 : }
523 :
524 217 : ChordLayout::layoutArticulations(c, ctx);
525 217 : ChordLayout::layoutArticulations2(c, ctx, true);
526 :
527 434 : for (Note* note : c->notes()) {
528 242 : for (EngravingItem* e : note->el()) {
529 25 : if (!e || !e->isFingering()) {
530 0 : continue;
531 : }
532 25 : Fingering* fingering = toFingering(e);
533 25 : if (fingering->isOnCrossBeamSide()) {
534 25 : TLayout::layoutFingering(fingering, fingering->mutldata());
535 25 : if (fingering->addToSkyline()) {
536 25 : const Note* n = fingering->note();
537 50 : const RectF r = fingering->ldata()->bbox().translated(
538 25 : fingering->pos() + n->pos() + n->chord()->pos() + segment.pos()
539 50 : + segment.measure()->pos());
540 25 : system->staff(fingering->note()->chord()->vStaffIdx())->skyline().add(r, fingering);
541 : }
542 : }
543 : }
544 : }
545 : }
546 : }
547 : }
548 7185 : }
549 :
550 : //---------------------------------------------------------
551 : // layoutPage
552 : // restHeight - vertical space which has to be distributed
553 : // between systems
554 : // The algorithm tries to produce most equally spaced
555 : // systems.
556 : //---------------------------------------------------------
557 :
558 4125 : void PageLayout::layoutPage(LayoutContext& ctx, Page* page, double restHeight, double footerPadding, bool squeezeToFit)
559 : {
560 4140 : TRACEFUNC;
561 4125 : if (restHeight < 0.0 && !squeezeToFit) {
562 : LOGN("restHeight < 0.0: %f\n", restHeight);
563 13 : restHeight = 0;
564 : }
565 :
566 4125 : int gaps = static_cast<int>(page->systems().size()) - 1;
567 :
568 4125 : std::vector<System*> sList;
569 :
570 : // build list of systems (excluding last)
571 : // set initial distance for each to the unstretched minimum distance to next
572 10195 : for (int i = 0; i < gaps; ++i) {
573 6070 : System* s1 = page->systems().at(i);
574 6070 : System* s2 = page->systems().at(i + 1);
575 6070 : s1->setDistance(s2->y() - s1->y());
576 6070 : if (s1->vbox() || s2->vbox() || s1->hasFixedDownDistance()) {
577 2398 : continue;
578 : }
579 3672 : sList.push_back(s1);
580 : }
581 :
582 5377 : if (sList.empty() || MScore::noVerticalStretch || ctx.conf().isVerticalSpreadEnabled()
583 5377 : || ctx.conf().viewMode() == LayoutMode::SYSTEM) {
584 3769 : if (ctx.conf().viewMode() == LayoutMode::FLOAT) {
585 0 : for (System* system : page->systems()) {
586 0 : system->move(PointF(0.0, 0.0));
587 : }
588 3769 : } else if ((ctx.conf().viewMode() != LayoutMode::SYSTEM) && ctx.conf().isVerticalSpreadEnabled()) {
589 3110 : distributeStaves(ctx, page, footerPadding, squeezeToFit);
590 : }
591 :
592 3769 : return;
593 : }
594 :
595 356 : const bool compactStaves = restHeight <= 0.0;
596 413 : auto pageFilled = [compactStaves](double space) -> bool {
597 413 : return compactStaves ? (space >= 0.0) : (space <= 0.0);
598 356 : };
599 :
600 356 : double maxDist = ctx.conf().maxSystemDistance();
601 :
602 : // allocate space as needed to normalize system distance (bottom of one system to top of next)
603 2670 : std::sort(sList.begin(), sList.end(), [](System* a, System* b) { return a->distance() - a->height() < b->distance() - b->height(); });
604 356 : System* s0 = sList[0];
605 356 : double dist = s0->distance() - s0->height(); // distance for shortest system
606 1415 : for (size_t i = 1; i < sList.size(); ++i) {
607 1075 : System* si = sList[i];
608 1075 : double ndist = si->distance() - si->height(); // next taller system
609 1075 : double fill = ndist - dist; // amount by which this system distance exceeds next shorter
610 1075 : if (fill > 0.0) {
611 413 : double totalFill = fill * static_cast<double>(i); // space required to add this amount to all shorter systems
612 413 : if (totalFill > restHeight) {
613 16 : totalFill = restHeight; // too much; adjust amount
614 16 : fill = restHeight / static_cast<double>(i);
615 : }
616 1527 : for (size_t k = 0; k < i; ++k) { // add amount to all shorter systems
617 1114 : System* s = sList[k];
618 1114 : double d = s->distance() + fill;
619 1114 : if ((d - s->height()) > maxDist) { // but don't exceed max system distance
620 190 : d = std::max(maxDist + s->height(), s->distance());
621 : }
622 1114 : s->setDistance(d);
623 : }
624 413 : restHeight -= totalFill; // reduce available space for next iteration
625 413 : if (pageFilled(restHeight)) {
626 16 : break; // no space left
627 : }
628 : }
629 1059 : dist = ndist; // set up for next iteration
630 : }
631 :
632 356 : if (restHeight > 0.0) { // space left?
633 340 : double fill = restHeight / static_cast<double>(sList.size());
634 1659 : for (System* s : sList) { // allocate it to systems equally
635 1319 : double d = s->distance() + fill;
636 1319 : if ((d - s->height()) > maxDist) { // but don't exceed max system distance
637 564 : d = std::max(maxDist + s->height(), s->distance());
638 : }
639 1319 : s->setDistance(d);
640 : }
641 : }
642 :
643 356 : double y = page->systems().at(0)->y();
644 2059 : for (int i = 0; i < gaps; ++i) {
645 1703 : System* s1 = page->systems().at(i);
646 1703 : s1->mutldata()->setPosY(y);
647 1703 : y += s1->distance();
648 : }
649 :
650 356 : page->systems().back()->mutldata()->setPosY(y);
651 7894 : }
652 :
653 3110 : void PageLayout::distributeStaves(LayoutContext& ctx, Page* page, double footerPadding, bool squeezeToFit)
654 : {
655 3110 : VerticalGapDataList vgdl;
656 :
657 : // Find and classify all gaps between staves.
658 3110 : int ngaps { 0 };
659 3110 : double prevYBottom { page->tm() };
660 3110 : double yBottom { 0.0 };
661 3110 : double spacerOffset { 0.0 };
662 3110 : bool vbox { false };
663 3110 : Spacer* nextSpacer { nullptr };
664 3110 : bool transferNormalBracket { false };
665 3110 : bool transferCurlyBracket { false };
666 10060 : for (System* system : page->systems()) {
667 6950 : if (system->vbox()) {
668 1592 : VerticalGapData* vgd = new VerticalGapData(&ctx.conf().style(), !ngaps++, system, nullptr, nullptr, nullptr, prevYBottom);
669 1592 : vgd->addSpaceAroundVBox(true);
670 1592 : prevYBottom = system->y();
671 1592 : yBottom = system->y() + system->height();
672 1592 : vbox = true;
673 1592 : vgdl.push_back(vgd);
674 1592 : transferNormalBracket = false;
675 1592 : transferCurlyBracket = false;
676 : } else {
677 5358 : bool newSystem { true };
678 5358 : bool addSpaceAroundNormalBracket { false };
679 5358 : bool addSpaceAroundCurlyBracket { false };
680 5358 : int endNormalBracket { -1 };
681 5358 : int endCurlyBracket { -1 };
682 5358 : int staffNr { -1 };
683 13766 : for (SysStaff* sysStaff : system->staves()) {
684 8408 : const Staff* staff = ctx.dom().staff(++staffNr);
685 8408 : IF_ASSERT_FAILED(staff) {
686 0 : break;
687 : }
688 8408 : addSpaceAroundNormalBracket |= endNormalBracket == staffNr;
689 8408 : addSpaceAroundCurlyBracket |= endCurlyBracket == staffNr;
690 11185 : for (const BracketItem* bi : ctx.dom().brackets(staffNr)) {
691 2777 : if (bi->bracketType() == BracketType::NORMAL) {
692 448 : addSpaceAroundNormalBracket |= int(staff->idx()) > (endNormalBracket - 1);
693 448 : endNormalBracket = std::max(endNormalBracket, int(staff->idx() + bi->bracketSpan()));
694 2329 : } else if (bi->bracketType() == BracketType::BRACE) {
695 946 : addSpaceAroundCurlyBracket |= int(staff->idx()) > (endCurlyBracket - 1);
696 946 : endCurlyBracket = std::max(endCurlyBracket, int(staff->idx() + bi->bracketSpan()));
697 : }
698 : }
699 :
700 8408 : if (!sysStaff->show()) {
701 199 : continue;
702 : }
703 :
704 : VerticalGapData* vgd
705 8209 : = new VerticalGapData(&ctx.conf().style(), !ngaps++, system, staff, sysStaff, nextSpacer, prevYBottom);
706 8209 : nextSpacer = system->downSpacer(staff->idx());
707 :
708 8209 : if (newSystem) {
709 5355 : vgd->addSpaceBetweenSections();
710 5355 : newSystem = false;
711 : }
712 8209 : if (addSpaceAroundNormalBracket || transferNormalBracket) {
713 465 : vgd->addSpaceAroundNormalBracket();
714 465 : addSpaceAroundNormalBracket = false;
715 465 : transferNormalBracket = false;
716 : }
717 8209 : if (addSpaceAroundCurlyBracket || transferCurlyBracket) {
718 1063 : vgd->addSpaceAroundCurlyBracket();
719 1063 : addSpaceAroundCurlyBracket = false;
720 1063 : transferCurlyBracket = false;
721 7146 : } else if (staffNr < endCurlyBracket) {
722 686 : vgd->insideCurlyBracket();
723 : }
724 :
725 8209 : if (vbox) {
726 1576 : vgd->addSpaceAroundVBox(false);
727 1576 : vbox = false;
728 : }
729 :
730 8209 : prevYBottom = system->y() + sysStaff->bbox().bottom();
731 8209 : yBottom = system->y() + sysStaff->y() + sysStaff->skyline().south().max();
732 8209 : spacerOffset = sysStaff->skyline().south().max() - sysStaff->bbox().height();
733 8209 : vgdl.push_back(vgd);
734 : }
735 5358 : transferNormalBracket = endNormalBracket >= 0;
736 5358 : transferCurlyBracket = endCurlyBracket >= 0;
737 : }
738 : }
739 3110 : --ngaps;
740 3110 : const double staffLowerBorder = ctx.conf().styleAbsolute(Sid::staffLowerBorder);
741 3110 : const double combinedBottomMargin = page->bm() + footerPadding;
742 3110 : const double marginToStaff = page->bm() + staffLowerBorder;
743 3110 : double spaceRemaining{ std::min(page->height() - combinedBottomMargin - yBottom, page->height() - marginToStaff - prevYBottom) };
744 :
745 3110 : if (nextSpacer) {
746 0 : spaceRemaining -= std::max(0.0, nextSpacer->absoluteGap() - spacerOffset - staffLowerBorder);
747 : }
748 3110 : if (spaceRemaining <= 0.0 && !squeezeToFit) {
749 2 : return;
750 : }
751 :
752 3108 : const bool compactStaves = spaceRemaining <= 0.0;
753 9587 : auto breakLoop = [compactStaves](double space) -> bool {
754 9587 : return compactStaves ? (space >= 0.0) : (space <= 0.0);
755 3108 : };
756 :
757 : // Try to make the gaps equal, taking the spread factors and maximum spacing into account.
758 : static const int maxPasses { 20 }; // Saveguard to prevent endless loops.
759 3108 : int pass { 0 };
760 4946 : while (!muse::RealIsNull(spaceRemaining) && (ngaps > 0) && (++pass < maxPasses)) {
761 3927 : ngaps = 0;
762 3927 : double smallest { vgdl.smallest() };
763 3927 : double nextSmallest { vgdl.smallest(smallest) };
764 3927 : if (muse::RealIsNull(smallest) || muse::RealIsNull(nextSmallest)) {
765 2089 : break;
766 : }
767 :
768 1838 : if ((nextSmallest - smallest) * vgdl.sumStretchFactor() > spaceRemaining) {
769 1018 : nextSmallest = smallest + spaceRemaining / vgdl.sumStretchFactor();
770 : }
771 :
772 1838 : double addedSpace { 0.0 };
773 1838 : VerticalGapDataList modified;
774 19010 : for (VerticalGapData* vgd : vgdl) {
775 17172 : if (!muse::RealIsNull(vgd->spacing() - smallest)) {
776 9423 : continue;
777 : }
778 7749 : double step { nextSmallest - vgd->spacing() };
779 7749 : if (step < 0.0) {
780 0 : continue;
781 : }
782 7749 : step = vgd->addSpacing(step);
783 7749 : if (!muse::RealIsNull(step)) {
784 7542 : addedSpace += step * vgd->factor();
785 7542 : modified.push_back(vgd);
786 7542 : ++ngaps;
787 : }
788 7749 : if (breakLoop(spaceRemaining - addedSpace)) {
789 0 : break;
790 : }
791 : }
792 1838 : if (breakLoop(spaceRemaining - addedSpace)) {
793 0 : for (VerticalGapData* vgd : modified) {
794 0 : vgd->undoLastAddSpacing();
795 : }
796 0 : ngaps = 0;
797 : } else {
798 1838 : spaceRemaining -= addedSpace;
799 : }
800 1838 : }
801 :
802 : // If there is still space left, distribute the space of the staves.
803 : // However, there is a limit on how much space is added per gap.
804 3108 : const double maxPageFill = ctx.conf().styleAbsolute(Sid::maxPageFillSpread);
805 3108 : spaceRemaining = std::min(maxPageFill * static_cast<double>(vgdl.size()), spaceRemaining);
806 3108 : pass = 0;
807 3108 : ngaps = 1;
808 7656 : while (!muse::RealIsNull(spaceRemaining) && !muse::RealIsNull(maxPageFill) && (ngaps > 0) && (++pass < maxPasses)) {
809 4548 : ngaps = 0;
810 4548 : double addedSpace { 0.0 };
811 4548 : double step { spaceRemaining / vgdl.sumStretchFactor() };
812 22399 : for (VerticalGapData* vgd : vgdl) {
813 17851 : double res { vgd->addFillSpacing(step, maxPageFill) };
814 17851 : if (!muse::RealIsNull(res)) {
815 7063 : addedSpace += res * vgd->factor();
816 7063 : ++ngaps;
817 : }
818 : }
819 4548 : spaceRemaining -= addedSpace;
820 : }
821 :
822 3108 : std::set<System*> systems;
823 3108 : double systemShift { 0.0 };
824 3108 : double staffShift { 0.0 };
825 3108 : System* prvSystem { nullptr };
826 12877 : for (VerticalGapData* vgd : vgdl) {
827 9769 : if (vgd->sysStaff) {
828 8177 : systems.insert(vgd->system);
829 : }
830 9769 : systemShift += vgd->actualAddedSpace();
831 9769 : if (prvSystem == vgd->system) {
832 2824 : staffShift += vgd->actualAddedSpace();
833 : } else {
834 6945 : vgd->system->mutldata()->moveY(systemShift);
835 6945 : if (prvSystem) {
836 3837 : prvSystem->setDistance(vgd->system->y() - prvSystem->y());
837 3837 : prvSystem->setHeight(prvSystem->height() + staffShift);
838 : }
839 6945 : staffShift = 0.0;
840 : }
841 :
842 9769 : if (vgd->sysStaff) {
843 8177 : vgd->sysStaff->bbox().translate(0.0, staffShift);
844 : }
845 :
846 9769 : prvSystem = vgd->system;
847 : }
848 3108 : if (prvSystem) {
849 3108 : prvSystem->setHeight(prvSystem->height() + staffShift);
850 : }
851 :
852 8461 : for (System* system : systems) {
853 5353 : SystemLayout::setMeasureHeight(system, system->height(), ctx);
854 5353 : SystemHeaderLayout::layoutBracketsVertical(system, ctx);
855 5353 : SystemHeaderLayout::setInstrumentNamesVerticalPos(system, ctx);
856 : }
857 3108 : vgdl.deleteAll();
858 3110 : }
859 :
860 4125 : void PageLayout::layoutSystemDividers(LayoutContext& ctx, Page* page)
861 : {
862 4125 : const std::vector<System*>& systems = page->systems();
863 14320 : for (size_t i = 0; i < systems.size(); ++i) {
864 10195 : System* system = systems[i];
865 10195 : if (system->vbox()) {
866 2371 : continue;
867 : }
868 :
869 7824 : System* nextSystem = i + 1 < systems.size() ? systems[i + 1] : nullptr;
870 :
871 7824 : bool needsDivider = nextSystem && !nextSystem->vbox();
872 7824 : bool needsLeftDivider = needsDivider && ctx.conf().styleB(Sid::dividerLeft);
873 7824 : bool needsRightDivider = needsDivider && ctx.conf().styleB(Sid::dividerRight);
874 :
875 7824 : updateSystemDivider(ctx, system, nextSystem, SystemDividerType::LEFT, needsLeftDivider);
876 7824 : updateSystemDivider(ctx, system, nextSystem, SystemDividerType::RIGHT, needsRightDivider);
877 : }
878 4125 : }
879 :
880 15648 : void PageLayout::updateSystemDivider(LayoutContext& ctx, System* system, System* nextSystem, SystemDividerType type, bool needsDivider)
881 : {
882 15648 : bool left = type == SystemDividerType::LEFT;
883 15648 : SystemDivider* divider = left ? system->systemDividerLeft() : system->systemDividerRight();
884 15648 : if (divider) {
885 6 : system->remove(divider);
886 : }
887 :
888 15648 : if (!needsDivider) {
889 15620 : return;
890 : }
891 :
892 28 : DO_ASSERT(system && nextSystem);
893 :
894 28 : Score* score = system->score();
895 28 : size_t systemIdx = muse::indexOf(score->systems(), system);
896 28 : IF_ASSERT_FAILED(systemIdx != muse::nidx) {
897 0 : return;
898 : }
899 :
900 28 : divider = score->systemDivider(systemIdx, type);
901 28 : if (!divider) {
902 14 : divider = new SystemDivider(system);
903 14 : divider->setDividerType(type);
904 14 : divider->setGenerated(true);
905 14 : score->addSystemDivider(systemIdx, divider);
906 : }
907 :
908 28 : system->add(divider);
909 :
910 28 : SystemDivider::LayoutData* ldata = divider->mutldata();
911 28 : TLayout::layoutSystemDivider(divider, ldata, ctx);
912 :
913 28 : RectF systemBBox = system->ldata()->bbox();
914 28 : double xDefault = 0.0;
915 28 : if (left) {
916 14 : if (Measure* firstM = system->firstMeasure(); firstM&& ctx.conf().styleB(Sid::dividerLeftAlignToSystemBarline)) {
917 : // Align to the outermost left system barline
918 14 : double leftMostSystem = firstM->x();
919 66 : for (const System* sys : system->page()->systems()) {
920 52 : if (sys->vbox()) {
921 4 : continue;
922 : }
923 48 : Measure* fm = sys->firstMeasure();
924 48 : if (fm) {
925 48 : leftMostSystem = std::min(leftMostSystem, fm->x());
926 : }
927 : }
928 14 : xDefault = leftMostSystem - 0.5 * ldata->bbox().width();
929 : } else {
930 0 : xDefault = 0.0;
931 : }
932 : } else {
933 14 : if (Measure* lastM = system->lastMeasure(); lastM&& ctx.conf().styleB(Sid::dividerRightAlignToSystemBarline)) {
934 : // Align to the outermost right system barline
935 14 : double rightMostSystem = lastM->x() + lastM->width();
936 66 : for (const System* sys : system->page()->systems()) {
937 52 : if (sys->vbox()) {
938 4 : continue;
939 : }
940 48 : Measure* lm = sys->lastMeasure();
941 48 : if (lm) {
942 48 : rightMostSystem = std::max(rightMostSystem, lm->x() + lm->width());
943 : }
944 : }
945 14 : xDefault = rightMostSystem - 0.5 * ldata->bbox().width();
946 : } else {
947 0 : xDefault = systemBBox.right() - ldata->bbox().width();
948 : }
949 : }
950 : double xPos = xDefault
951 28 : + system->absoluteFromSpatium(left ? ctx.conf().styleS(Sid::dividerLeftX) : ctx.conf().styleS(Sid::dividerRightX));
952 :
953 28 : double yInnerPos = -ldata->bbox().top() - 0.5 * ldata->bbox().height()
954 28 : + system->absoluteFromSpatium(left ? ctx.conf().styleS(Sid::dividerLeftY) : ctx.conf().styleS(Sid::dividerRightY));
955 :
956 28 : SysStaff* lastVisibleOfThis = system->staff(system->lastVisibleStaff());
957 28 : double bottomOfThisSystem = lastVisibleOfThis->bbox().bottom();
958 :
959 28 : SysStaff* firstVisibleOfNext = system->staff(system->firstVisibleStaff());
960 28 : double yNextSystem = nextSystem->pagePos().y() - system->pagePos().y();
961 28 : double topOfNextSystem = yNextSystem + firstVisibleOfNext->bbox().top();
962 :
963 28 : double yMid = 0.5 * (bottomOfThisSystem + topOfNextSystem);
964 :
965 28 : ldata->setPos(xPos, yInnerPos + yMid);
966 : }
|