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) 2024 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 :
23 : #include "scorerangeutilities.h"
24 :
25 : #include "engraving/dom/box.h"
26 : #include "engraving/dom/measure.h"
27 : #include "engraving/dom/page.h"
28 : #include "engraving/dom/score.h"
29 : #include "engraving/dom/segment.h"
30 : #include "engraving/dom/staff.h"
31 : #include "engraving/dom/system.h"
32 :
33 : using namespace mu::notation;
34 : using namespace mu::engraving;
35 :
36 0 : static const Box* getLeadingTrailingBox(const System* system, bool leading)
37 : {
38 0 : IF_ASSERT_FAILED(system) {
39 0 : return nullptr;
40 : }
41 0 : const MeasureBase* mb = leading ? system->first() : system->last();
42 0 : return mb && mb->isBox() ? toBox(mb) : nullptr;
43 : }
44 :
45 : // Unites the bounding rects in a run of consecutive boxes (until "limit")...
46 0 : static RectF boxRunBoundingRect(const Box* box, bool forwards, const Box* limit = nullptr)
47 : {
48 0 : RectF rect = box->canvasBoundingRect();
49 0 : const System* system = box->system();
50 :
51 0 : const MeasureBase* mb = box;
52 0 : while (mb != limit) {
53 0 : mb = forwards ? mb->nextMM() : mb->prevMM();
54 0 : if (!mb || !mb->isBox() || mb->system() != system) {
55 0 : break;
56 : }
57 0 : rect.unite(mb->canvasBoundingRect());
58 : }
59 :
60 0 : if (!rect.isEmpty()) {
61 0 : const double padding = 0.5 * box->spatium();
62 0 : rect.adjust(0, -padding, 0, padding);
63 : }
64 :
65 0 : return rect;
66 : }
67 :
68 0 : static std::set<const System*> boxOnlySystems(const System* startSystem, const System* endSystem)
69 : {
70 0 : std::set<const System*> result;
71 0 : IF_ASSERT_FAILED(startSystem) {
72 0 : return result;
73 : }
74 0 : const System* currSystem = startSystem;
75 0 : while (currSystem) {
76 0 : bool isBoxOnlySystem = true;
77 0 : const MeasureBase* mb = currSystem->first();
78 0 : while (mb && mb->system() == currSystem) {
79 0 : if (!mb->isBox()) {
80 0 : isBoxOnlySystem = false;
81 0 : break;
82 : }
83 0 : mb = mb->nextMM();
84 : }
85 0 : if (isBoxOnlySystem) {
86 0 : result.emplace(currSystem);
87 : }
88 0 : if (currSystem == endSystem) {
89 0 : break;
90 : }
91 0 : const MeasureBase* firstInNext = currSystem->last() ? currSystem->last()->nextMM() : nullptr;
92 0 : currSystem = firstInNext ? firstInNext->system() : nullptr;
93 : }
94 0 : return result;
95 0 : }
96 :
97 0 : std::vector<muse::RectF> ScoreRangeUtilities::boundingArea(const Score* score,
98 : const Segment* startSegment, const Segment* endSegment,
99 : staff_idx_t startStaffIndex, staff_idx_t endStaffIndex,
100 : const engraving::Box* startBox,
101 : const engraving::Box* endBox)
102 : {
103 0 : if (!score || !startSegment || !endSegment || startSegment->tick() > endSegment->tick()) {
104 0 : return {};
105 : }
106 :
107 0 : const std::vector<RangeSection> sections = splitRangeBySections(startSegment, endSegment);
108 :
109 0 : std::vector<RectF> result;
110 :
111 0 : if (sections.empty()) {
112 : // Box-only range...
113 0 : if (!startBox && !endBox) {
114 0 : return result;
115 : }
116 0 : IF_ASSERT_FAILED(startBox && endBox) {
117 0 : return result;
118 : }
119 0 : const Box* currBox = startBox;
120 0 : const System* endSystem = endBox->system();
121 0 : while (currBox) {
122 0 : const System* currSystem = currBox->system();
123 0 : IF_ASSERT_FAILED(currSystem) {
124 0 : break;
125 : }
126 0 : result.push_back(boxRunBoundingRect(currBox, /*forwards*/ true, endBox));
127 0 : if (currSystem == endSystem) {
128 0 : break;
129 : }
130 0 : const MeasureBase* firstInNext = currSystem->last() ? currSystem->last()->nextMM() : nullptr;
131 0 : currBox = firstInNext && firstInNext->isBox() ? toBox(firstInNext) : nullptr;
132 : }
133 0 : return result;
134 : }
135 :
136 : //! NOTE: endSegment is exclusive, so these are the systems of the first and last segments that are actually inside
137 : //! the range...
138 0 : const System* firstSegmentSystem = sections.front().system;
139 0 : const System* lastSegmentSystem = sections.back().system;
140 :
141 : // Collect and add systems consisting solely of boxes...
142 0 : const System* startSystem = startBox ? startBox->system() : firstSegmentSystem;
143 0 : const System* endSystem = endBox ? endBox->system() : lastSegmentSystem;
144 0 : const std::set<const System*> boxSystems = startSystem ? boxOnlySystems(startSystem, endSystem) : std::set<const System*>();
145 0 : for (const System* boxOnlySystem : boxSystems) {
146 0 : const bool useStartBox = startBox && startBox->system() == boxOnlySystem;
147 0 : const MeasureBase* first = useStartBox ? startBox : boxOnlySystem->first();
148 :
149 0 : const bool useEndBox = endBox && endBox->system() == boxOnlySystem;
150 0 : const MeasureBase* last = useEndBox ? endBox : boxOnlySystem->last();
151 :
152 0 : IF_ASSERT_FAILED(first && first->isBox() && last && last->isBox()) {
153 0 : continue;
154 : }
155 :
156 0 : result.push_back(boxRunBoundingRect(toBox(first), /*forwards*/ true, toBox(last)));
157 : }
158 :
159 0 : if (!score->linearMode()) {
160 : // Handle start/end boxes that exist on a different system to the start/end segment...
161 0 : if (startBox && startBox->system() != firstSegmentSystem && boxSystems.find(startBox->system()) == boxSystems.end()) {
162 0 : result.push_back(boxRunBoundingRect(startBox, /*forwards*/ true));
163 : }
164 0 : if (endBox && endBox->system() != lastSegmentSystem && boxSystems.find(endBox->system()) == boxSystems.end()) {
165 0 : result.push_back(boxRunBoundingRect(endBox, /*forwards*/ false));
166 : }
167 : }
168 :
169 0 : for (size_t i = 0; i < sections.size(); ++i) {
170 0 : const RangeSection& section = sections.at(i);
171 :
172 0 : const staff_idx_t firstStaff = firstVisibleStaffIdx(score, section.system, startStaffIndex);
173 0 : const staff_idx_t lastStaff = lastVisibleStaffIdx(score, section.system, endStaffIndex);
174 0 : if (firstStaff == muse::nidx || lastStaff == muse::nidx) {
175 0 : continue;
176 : }
177 :
178 0 : const Box* sectionStartBox = nullptr;
179 0 : if (startBox && startBox->system() == section.system) {
180 : // startBox is on this system - it's the "section start box"...
181 0 : sectionStartBox = startBox;
182 0 : } else if (i > 0 || startBox) {
183 : // startBox exists but isn't on this system or the range started earlier - the section start box is the "leading" box...
184 0 : sectionStartBox = getLeadingTrailingBox(section.system, /*leading*/ true);
185 : }
186 0 : const Box* sectionEndBox = nullptr;
187 0 : if (endBox && endBox->system() == section.system) {
188 : // endBox is on this system - it's the "section end box"...
189 0 : sectionEndBox = endBox;
190 0 : } else if (i < sections.size() - 1 || endBox) {
191 : // endBox exists but isn't on this system or the range is ending later - the section end box is the "trailing" box...
192 0 : sectionEndBox = getLeadingTrailingBox(section.system, /*leading*/ false);
193 : }
194 :
195 0 : const SysStaff* segmentFirstStaff = section.system->staff(firstStaff);
196 0 : const SysStaff* segmentLastStaff = section.system->staff(lastStaff);
197 :
198 0 : const Staff* scoreFirstStaff = score->staff(firstStaff);
199 0 : const Staff* scoreLastStaff = score->staff(lastStaff);
200 :
201 0 : const double standardStaffHeight = 4 * scoreFirstStaff->spatium(Fraction(0, 1));
202 0 : const double firstStaffHeight = scoreFirstStaff->staffHeight();
203 0 : const double lastStaffHeight = scoreLastStaff->staffHeight();
204 :
205 0 : double topY = 0.0;
206 0 : if (firstStaffHeight < standardStaffHeight) {
207 0 : const double diff = standardStaffHeight - firstStaffHeight;
208 0 : topY -= 0.5 * diff;
209 : }
210 :
211 0 : double bottomY = lastStaffHeight;
212 0 : if (lastStaffHeight < standardStaffHeight) {
213 0 : const double diff = standardStaffHeight - lastStaffHeight;
214 0 : bottomY += 0.5 * diff;
215 : }
216 :
217 0 : double x1 = 0.0;
218 0 : if (sectionStartBox) {
219 0 : x1 = sectionStartBox->pageBoundingRect().left();
220 : } else {
221 0 : x1 = section.startSegment->pagePos().x();
222 : }
223 :
224 0 : double x2 = 0.0;
225 0 : if (sectionEndBox) {
226 0 : x2 = sectionEndBox->pageBoundingRect().right();
227 : } else {
228 0 : x2 = section.endSegment->pageBoundingRect().right();
229 : }
230 :
231 0 : const int padding = 0.5 * scoreFirstStaff->spatium(startSegment->tick());
232 0 : const double y1 = topY + segmentFirstStaff->y() + section.startSegment->pagePos().y() - padding;
233 0 : const double y2 = bottomY + segmentLastStaff->y() + section.endSegment->pagePos().y() + padding;
234 :
235 0 : if (!sectionStartBox && section.startSegment->measure()->firstEnabled() == section.startSegment) {
236 0 : x1 = section.startSegment->measure()->pagePos().x();
237 : }
238 :
239 0 : const RectF rect = RectF(PointF(x1, y1), PointF(x2, y2)).translated(section.system->page()->pos());
240 0 : result.push_back(rect);
241 : }
242 :
243 0 : return result;
244 0 : }
245 :
246 0 : std::vector<ScoreRangeUtilities::RangeSection> ScoreRangeUtilities::splitRangeBySections(
247 : const Segment* rangeStartSegment,
248 : const Segment* rangeEndSegment)
249 : {
250 0 : std::vector<RangeSection> sections;
251 :
252 0 : const Segment* startSegment = rangeStartSegment;
253 0 : const Fraction rangeEndTick = rangeEndSegment->tick();
254 :
255 0 : for (const Segment* segment = startSegment; segment && segment != rangeEndSegment && segment->tick() < rangeEndTick;) {
256 0 : const System* currentSegmentSystem = segment->measure()->system();
257 :
258 0 : const Segment* nextSegment = segment->next1MMenabled();
259 0 : while (nextSegment && !nextSegment->visible()) {
260 0 : nextSegment = nextSegment->next1MMenabled();
261 : }
262 :
263 0 : if (!nextSegment) {
264 0 : RangeSection section;
265 0 : section.system = currentSegmentSystem;
266 0 : section.startSegment = startSegment;
267 0 : section.endSegment = segment;
268 :
269 0 : sections.push_back(section);
270 0 : break;
271 : }
272 :
273 0 : const System* nextSegmentSystem = nextSegment->measure()->system();
274 0 : if (!nextSegmentSystem) {
275 0 : const Measure* mmr = nextSegment->measure()->coveringMMRestOrThis();
276 0 : if (mmr) {
277 0 : nextSegmentSystem = mmr->system();
278 : }
279 0 : if (!nextSegmentSystem) {
280 0 : break;
281 : }
282 : }
283 :
284 0 : if (nextSegmentSystem != currentSegmentSystem || nextSegment->tick() >= rangeEndTick) {
285 0 : RangeSection section;
286 0 : section.system = currentSegmentSystem;
287 0 : section.startSegment = startSegment;
288 0 : section.endSegment = segment;
289 :
290 0 : sections.push_back(section);
291 0 : startSegment = nextSegment;
292 : }
293 :
294 0 : segment = nextSegment;
295 : }
296 :
297 0 : return sections;
298 0 : }
299 :
300 0 : staff_idx_t ScoreRangeUtilities::firstVisibleStaffIdx(const Score* score, const System* system, staff_idx_t startStaffIndex)
301 : {
302 0 : for (staff_idx_t i = startStaffIndex; i < score->nstaves(); ++i) {
303 0 : if (system->staff(i)->show()) {
304 0 : return i;
305 : }
306 : }
307 :
308 0 : return muse::nidx;
309 : }
310 :
311 0 : staff_idx_t ScoreRangeUtilities::lastVisibleStaffIdx(const Score*, const System* system, staff_idx_t endStaffIndex)
312 : {
313 0 : for (int i = static_cast<int>(endStaffIndex) - 1; i >= 0; --i) {
314 0 : if (system->staff(i)->show()) {
315 0 : return static_cast<staff_idx_t>(i);
316 : }
317 : }
318 :
319 0 : return muse::nidx;
320 : }
|