File size: 5,431 Bytes
af6912c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
var layoutVoice = require('./voice');
var setUpperAndLowerElements = require('./set-upper-and-lower-elements');
var layoutStaffGroup = require('./staff-group');
var getLeftEdgeOfStaff = require('./get-left-edge-of-staff');
var layoutInGrid = require('./layout-in-grid');
// This sets the "x" attribute on all the children in abctune.lines
// It also sets the "w" and "startx" attributes on "voices"
// It also sets the "w" and "startx" attributes on "voices.children"
var layout = function (renderer, abctune, width, space, expandToWidest, timeBasedLayout) {
var i;
var abcLine;
// Adjust the x-coordinates to their absolute positions
var maxWidth = width;
for (i = 0; i < abctune.lines.length; i++) {
abcLine = abctune.lines[i];
if (abcLine.staff) {
// console.log("=== line", i)
var thisWidth;
if (timeBasedLayout !== undefined)
thisWidth = layoutInGrid(renderer, abcLine.staffGroup, timeBasedLayout);
else
thisWidth = setXSpacing(renderer, maxWidth, space, abcLine.staffGroup, abctune.formatting, i === abctune.lines.length - 1, false);
// console.log(thisWidth, maxWidth)
if (Math.round(thisWidth) > Math.round(maxWidth)) { // to take care of floating point weirdness
maxWidth = thisWidth
if (expandToWidest)
i = -1 // do the calculations over with the new width
}
}
}
// Layout the beams and add the stems to the beamed notes.
for (i = 0; i < abctune.lines.length; i++) {
abcLine = abctune.lines[i];
if (abcLine.staffGroup && abcLine.staffGroup.voices) {
for (var j = 0; j < abcLine.staffGroup.voices.length; j++)
layoutVoice(abcLine.staffGroup.voices[j]);
setUpperAndLowerElements(renderer, abcLine.staffGroup);
}
}
// Set the staff spacing
// TODO-PER: we should have been able to do this by the time we called setUpperAndLowerElements, but for some reason the "bottom" element seems to be set as a side effect of setting the X spacing.
for (i = 0; i < abctune.lines.length; i++) {
abcLine = abctune.lines[i];
if (abcLine.staffGroup) {
abcLine.staffGroup.setHeight();
}
}
return maxWidth;
}
// Do the x-axis positioning for a single line (a group of related staffs)
var setXSpacing = function (renderer, width, space, staffGroup, formatting, isLastLine, debug) {
var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket);
var newspace = space;
//dumpGroup("before", staffGroup)
for (var it = 0; it < 8; it++) { // TODO-PER: shouldn't need multiple passes, but each pass gets it closer to the right spacing. (Only affects long lines: normal lines break out of this loop quickly.)
// console.log("iteration", it)
var ret = layoutStaffGroup(newspace, renderer.minPadding, debug, staffGroup, leftEdge);
newspace = calcHorizontalSpacing(isLastLine, formatting.stretchlast, width + renderer.padding.left, staffGroup.w, newspace, ret.spacingUnits, ret.minSpace, renderer.padding.left + renderer.padding.right);
if (debug)
console.log("setXSpace", it, staffGroup.w, newspace, staffGroup.minspace);
if (newspace === null) break;
}
//dumpGroup("after",staffGroup)
centerWholeRests(staffGroup.voices);
return staffGroup.w - leftEdge
};
function replacer(key, value) {
// Filtering out properties
if (key === 'parent') {
return 'parent';
}
if (key === 'beam') {
return 'beam';
}
return value;
}
function dumpGroup(label, staffGroup) {
console.log("=================== " + label + " =========================")
console.log(staffGroup)
console.log(JSON.stringify(staffGroup, replacer, "\t"))
}
function calcHorizontalSpacing(isLastLine, stretchLast, targetWidth, lineWidth, spacing, spacingUnits, minSpace, padding) {
if (isLastLine) {
if (stretchLast === undefined) {
if (lineWidth / targetWidth < 0.66) return null; // keep this for backward compatibility. The break isn't quite the same for some reason.
} else {
// "Stretch the last music line of a tune when it lacks less than the float fraction of the page width."
var lack = 1 - (lineWidth + padding) / targetWidth;
var stretch = lack < stretchLast;
if (!stretch) return null; // don't stretch last line too much
}
}
if (Math.abs(targetWidth - lineWidth) < 2) return null; // if we are already near the target width, we're done.
var relSpace = spacingUnits * spacing;
var constSpace = lineWidth - relSpace;
if (spacingUnits > 0) {
spacing = (targetWidth - constSpace) / spacingUnits;
if (spacing * minSpace > 50) {
spacing = 50 / minSpace;
}
return spacing;
}
return null;
}
function centerWholeRests(voices) {
// whole rests are a special case: if they are by themselves in a measure, then they should be centered.
// (If they are not by themselves, that is probably a user error, but we'll just center it between the two items to either side of it.)
for (var i = 0; i < voices.length; i++) {
var voice = voices[i];
// Look through all of the elements except for the first and last. If the whole note appears there then there isn't anything to center it between anyway.
for (var j = 1; j < voice.children.length - 1; j++) {
var absElem = voice.children[j];
if (absElem.abcelem.rest && (absElem.abcelem.rest.type === 'whole' || absElem.abcelem.rest.type === 'multimeasure')) {
var before = voice.children[j - 1];
var after = voice.children[j + 1];
absElem.center(before, after);
}
}
}
}
module.exports = layout;
|