package com.xenoage.zong.musiclayout.spacer.frame;
import com.xenoage.utils.math.geom.Size2f;
import com.xenoage.zong.core.Score;
import com.xenoage.zong.core.format.ScoreFormat;
import com.xenoage.zong.core.format.SystemLayout;
import com.xenoage.zong.core.header.ScoreHeader;
import com.xenoage.zong.core.position.MP;
import com.xenoage.zong.musiclayout.layouter.Context;
import com.xenoage.zong.musiclayout.notation.Notations;
import com.xenoage.zong.musiclayout.spacing.ColumnSpacing;
import com.xenoage.zong.musiclayout.spacing.FrameSpacing;
import com.xenoage.zong.musiclayout.spacing.SystemSpacing;
import java.util.List;
import static com.xenoage.utils.collections.CollectionUtils.alist;
import static com.xenoage.zong.core.position.MP.atMeasure;
import static com.xenoage.zong.musiclayout.spacer.system.SystemSpacer.systemSpacer;
/**
* Arranges a list of measure columns on a frame by filling it with systems.
*
* The systems are not stretched or moved horizontally or vertically.
* This can be done by a later layouting step.
*
* This strategy also regards custom system distances.
*
* @author Andreas Wenger
*/
public class FrameSpacer {
public static final FrameSpacer frameSpacer = new FrameSpacer();
/**
* Arranges an optimum number of systems in a frame, beginning at the given measure,
* if possible.
* @param context the context of the layouter, with the {@link MP} set to the start measure
* @param startSystem the index of the system where to start
* @param usableSizeMm the usable size within the score frame in mm
* @param isAdditional true, when this frame is created for a complete score layout,
* but is not part of the defined layout
* @param columnSpacings a list of all measure column spacings without leading spacings
* @param notations the notations of the elements, needed when a column has to be recomputed
* because of a leading spacing
*/
public FrameSpacing compute(Context context, int startSystem, Size2f usableSizeMm,
boolean isAdditional, List<ColumnSpacing> columnSpacings, Notations notations) {
context.saveMp();
int startMeasure = context.mp.measure;
Score score = context.score;
ScoreFormat scoreFormat = score.getFormat();
ScoreHeader scoreHeader = score.getHeader();
int measuresCount = score.getMeasuresCount();
int measureIndex = startMeasure;
int systemIndex = startSystem;
//top margin
float offsetY = getTopSystemDistance(systemIndex, scoreFormat, scoreHeader);
//append systems to the frame
List<SystemSpacing> systems = alist();
while (measureIndex < measuresCount) {
//try to create system on this frame
context.mp = atMeasure(measureIndex);
boolean isFirst = (systems.size() == 0);
SystemSpacing system = systemSpacer.compute(context, usableSizeMm, offsetY, systemIndex,
isFirst, isAdditional, columnSpacings, notations).orNull();
//was there enough place for this system?
if (system != null) {
//yes, there is enough place. add system
systems.add(system);
//update offset and start measure index for next system
//add height of this system
offsetY += system.getHeightMm();
//add system distance of the following system
offsetY += getSystemDistance(systemIndex + 1, scoreFormat, scoreHeader);
//increase indexes
systemIndex++;
measureIndex = system.getEndMeasure() + 1;
}
else {
break;
}
}
context.restoreMp();
return new FrameSpacing(systems, usableSizeMm);
}
/**
* Returns the top system distance (distance between top margin of the frame and
* first staff line of the first system within the frame) for the given
* system (global index) in mm.
*/
private float getTopSystemDistance(int systemIndex, ScoreFormat scoreFormat,
ScoreHeader scoreHeader) {
SystemLayout systemLayout = scoreHeader.getSystemLayout(systemIndex);
if (systemLayout != null) {
//use custom top system distance
return systemLayout.getDistance();
}
else {
//use default distance
return scoreFormat.getTopSystemDistance();
}
}
/**
* Returns the system distance (distance between last staff line of the previous
* system and first staff line of the following given system (global index) in mm.
*/
private float getSystemDistance(int systemIndex, ScoreFormat scoreFormat, ScoreHeader scoreHeader) {
SystemLayout systemLayout = scoreHeader.getSystemLayout(systemIndex);
if (systemLayout != null) {
//use custom system distance
return systemLayout.getDistance();
}
else {
//use default system distance
return scoreFormat.getSystemLayout().getDistance();
}
}
}