Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/include/mx/api/SystemLayoutData.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "mx/api/LeftRight.h"
#include "mx/api/PageLayoutData.h"

#include <map>
#include <string>
#include <vector>

Expand All @@ -29,21 +30,29 @@ class SystemLayoutData
/// Distance from the top margin of the page to the top line of the first staff on the page, in tenths.
OptionalDouble topSystemDistance;

/// the space between staves within the same system, in tenths.
/// The space between staves within the same system, in tenths. Applies to every staff of a part
/// that does not have its own entry in staffDistances.
OptionalDouble staffDistance;

/// Per-staff spacing for multi-staff parts, keyed by zero-based staff index: the space between
/// the bottom line of the previous staff and the top line of the keyed staff, in tenths.
/// Normally empty; only add an entry when one staff needs its own spacing, e.g. extra room
/// above an organ's pedal staff. For a given staff, an entry here wins; otherwise
/// staffDistance applies.
std::map<int, double> staffDistances;

/// Returns true if any of the members have values.
inline bool isUsed() const
{
return margins || systemDistance || topSystemDistance || staffDistance;
return margins || systemDistance || topSystemDistance || staffDistance || !staffDistances.empty();
}

explicit inline SystemLayoutData(std::optional<LeftRight> inMargins = std::nullopt,
OptionalDouble inSystemDistance = std::nullopt,
OptionalDouble inTopSystemDistance = std::nullopt,
OptionalDouble inStaffDistance = std::nullopt)
: margins(inMargins), systemDistance(inSystemDistance), topSystemDistance{inTopSystemDistance},
staffDistance(inStaffDistance)
staffDistance(inStaffDistance), staffDistances{}
{
}
};
Expand All @@ -53,6 +62,7 @@ MXAPI_EQUALS_MEMBER(margins)
MXAPI_EQUALS_MEMBER(systemDistance)
MXAPI_EQUALS_MEMBER(topSystemDistance)
MXAPI_EQUALS_MEMBER(staffDistance)
MXAPI_EQUALS_MEMBER(staffDistances)
MXAPI_EQUALS_END;
MXAPI_NOT_EQUALS_AND_VECTORS(SystemLayoutData);
} // namespace api
Expand Down
34 changes: 26 additions & 8 deletions src/private/mx/impl/LayoutFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,15 @@ void addSystemMargins(const api::DefaultsData &inDefaults, core::ScoreHeaderGrou
needsDefaults = true;
}

for (const auto &[staffIndex, staffDistance] : inDefaults.systemLayout.staffDistances)
{
core::StaffLayout staffLayout;
staffLayout.setNumber(core::StaffNumber{staffIndex + 1});
staffLayout.setStaffDistance(toTenths(staffDistance));
layout.addStaffLayout(staffLayout);
needsDefaults = true;
}

if (needsDefaults)
{
layout.setSystemLayout(systemLayout);
Expand Down Expand Up @@ -427,15 +436,24 @@ void addStaffLayout(const core::ScoreHeaderGroup &inScoreHeaderGroup, api::Defau
return;
}

const auto &staffLayouts = inScoreHeaderGroup.defaults()->layout().staffLayout();
if (staffLayouts.empty())
{
return;
}

if (staffLayouts.front().staffDistance().has_value())
// A source with an explicit number attribute is staff-scoped (a staffDistances map entry);
// one with no number applies to staff 1 by schema default and is captured as the plain
// unscoped value. Mirrors ScoreReader::scanForSystemInfo's per-measure handling.
for (const auto &staffLayout : inScoreHeaderGroup.defaults()->layout().staffLayout())
{
outDefaults.systemLayout.staffDistance = staffLayouts.front().staffDistance()->value().value();
if (!staffLayout.staffDistance().has_value())
{
continue;
}
const double distance = staffLayout.staffDistance()->value().value();
if (staffLayout.number().has_value())
{
outDefaults.systemLayout.staffDistances[staffLayout.number()->value() - 1] = distance;
}
else
{
outDefaults.systemLayout.staffDistance = distance;
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/private/mx/impl/MeasureWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "mx/core/generated/Repeat.h"
#include "mx/core/generated/RightLeftMiddle.h"
#include "mx/core/generated/StaffLayout.h"
#include "mx/core/generated/StaffNumber.h"
#include "mx/core/generated/SystemLayout.h"
#include "mx/core/generated/SystemMargins.h"
#include "mx/core/generated/Tenths.h"
Expand Down Expand Up @@ -263,6 +264,15 @@ void MeasureWriter::writeSystemInfo()
outLayoutGroup.addStaffLayout(outStaffLayout);
outPrint.setLayout(outLayoutGroup);
}

for (const auto &[staffIndex, staffDistance] : inSystemLayout.staffDistances)
{
core::StaffLayout outStaffLayout{};
outStaffLayout.setNumber(core::StaffNumber{staffIndex + 1});
outStaffLayout.setStaffDistance(core::Tenths{core::Decimal{staffDistance}});
outLayoutGroup.addStaffLayout(outStaffLayout);
outPrint.setLayout(outLayoutGroup);
}
}

myOutMeasure.addMusicData(core::MusicDataChoice::print(outPrint));
Expand Down
20 changes: 14 additions & 6 deletions src/private/mx/impl/ScoreReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -466,15 +466,23 @@ void ScoreReader::scanForSystemInfo() const
}
}

// Per-measure <staff-layout> staff-distance. (We model a single
// staff-distance, matching how the defaults staff-layout is
// mapped; the first staff-layout's distance is used.)
// Per-measure <staff-layout> staff-distance. A source with an explicit number
// attribute is staff-scoped (a staffDistances map entry); one with no number applies
// to staff 1 by schema default and is captured as the plain unscoped value.
for (const auto &staffLayout : layoutGroup.staffLayout())
{
if (staffLayout.staffDistance().has_value())
if (!staffLayout.staffDistance().has_value())
{
systemData.layout.staffDistance = staffLayout.staffDistance()->value().value();
break;
continue;
}
const double distance = staffLayout.staffDistance()->value().value();
if (staffLayout.number().has_value())
{
systemData.layout.staffDistances[staffLayout.number()->value() - 1] = distance;
}
else
{
systemData.layout.staffDistance = distance;
}
}

Expand Down
28 changes: 28 additions & 0 deletions src/private/mxtest/api/PrintLayoutRoundTripTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,32 @@ TEST(printLayoutRoundTrip, perMeasureStaffDistance)
CHECK_EQUAL(120.0, sysOut.layout.systemDistance.value());
}

// A <staff-layout number="N"> is scoped to one staff of a multi-staff part; the writer
// only ever emitted an unscoped <staff-layout> (matching SystemLayoutData::staffDistance),
// and the reader dropped the number attribute on the one it read back, silently
// misattributing the distance to staff 1. Verify a staff-scoped distance survives via
// SystemLayoutData::staffDistances.
TEST(printLayoutRoundTrip, staffScopedStaffDistance)
{
auto in = makeScoreWithMeasures(1);
auto &staff0 = in.parts.front().measures.front().staves.front();
StaffData staff1 = staff0;
in.parts.front().measures.front().staves.push_back(staff1);

SystemData sys{};
sys.layout.staffDistances[1] = 65.0;
in.layout.emplace(0, LayoutData{sys, PageData{}});

const auto out = mxtest::roundTrip(in);

const auto it = out.layout.find(0);
REQUIRE(it != out.layout.end());
const auto &sysOut = it->second.system;
CHECK(!static_cast<bool>(sysOut.layout.staffDistance));
CHECK_EQUAL(1, static_cast<int>(sysOut.layout.staffDistances.size()));
const auto sdIt = sysOut.layout.staffDistances.find(1);
REQUIRE(sdIt != sysOut.layout.staffDistances.end());
CHECK_EQUAL(65.0, sdIt->second);
}

#endif
Loading