@stringsync/vexml
Version:
MusicXML to Vexflow
254 lines (253 loc) • 11.2 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Fragment = void 0;
const vexflow = __importStar(require("vexflow"));
const spatial_1 = require("../spatial");
const part_1 = require("./part");
const pen_1 = require("./pen");
const partlabelgroup_1 = require("./partlabelgroup");
const budget_1 = require("./budget");
const ensemble_1 = require("./ensemble");
const gapoverlay_1 = require("./gapoverlay");
const BARLINE_PADDING_RIGHT = 6;
const MEASURE_NUMBER_PADDING_LEFT = 6;
const BRACE_CONNECTOR_PADDING_LEFT = 8;
class Fragment {
config;
log;
document;
key;
position;
width;
constructor(config, log, document, key, position, width) {
this.config = config;
this.log = log;
this.document = document;
this.key = key;
this.position = position;
this.width = width;
}
render() {
const pen = new pen_1.Pen(this.position);
let widthBudget;
if (this.width === null) {
const minWidth = this.document.getFragment(this.key).minWidth;
if (minWidth) {
widthBudget = new budget_1.Budget(minWidth);
}
else {
widthBudget = budget_1.Budget.unlimited();
}
}
else {
widthBudget = new budget_1.Budget(this.width);
}
// We don't know the y positions of the staves yet, so we don't know the y positions of the labels yet. For now,we
// just account for the width it steals from the parts.
const prePartLabelGroupPosition = pen.position();
this.accountForPartLabelGroupWidth(pen, widthBudget);
const postPartLabelGroupPosition = pen.position();
// Drawing some vexflow elements is not idempotent. However, this is needed to determine the rects of the components
// in vexml. We render the children twice. The first render will be used to get the rect measurements, then thrown
// away. The second render will actually be used to draw the elements.
// See https://github.com/vexflow/vexflow/issues/254
pen.save();
const partRenders = this.renderParts(pen);
pen.restore();
const throwawayPartRenders = this.renderParts(pen);
const fragmentRender = {
type: 'fragment',
key: this.key,
rectSrc: 'none',
rect: spatial_1.Rect.empty(), // placeholder
excessHeight: 0, // placeholder
partLabelGroupRender: null, // placeholder
vexflowStaveConnectors: [], // placeholder
partRenders,
gapOverlay: null, // placeholder
};
const throwawayFragmentRender = {
type: 'fragment',
key: this.key,
rectSrc: 'none',
rect: spatial_1.Rect.empty(), // placeholder
excessHeight: 0, // placeholder
partLabelGroupRender: null, // placeholder
vexflowStaveConnectors: [], // placeholder
partRenders: throwawayPartRenders,
gapOverlay: null, // placeholder
};
let ensembleWidth;
if (widthBudget.isUnlimited() || widthBudget.remaining() <= 0) {
ensembleWidth = null;
}
else {
ensembleWidth = widthBudget.remaining();
}
const paddingLeft = pen.x - postPartLabelGroupPosition.x;
let paddingRight = 0;
const isLastMeasure = this.document.isLastMeasure(this.key);
const isLastFragment = this.document.isLastFragment(this.key);
if (isLastMeasure && isLastFragment) {
paddingRight += BARLINE_PADDING_RIGHT;
}
const ensemble = new ensemble_1.Ensemble(this.config, this.log, this.document, this.key);
// First format is to populate the cache.
ensemble.format(throwawayFragmentRender, {
x: postPartLabelGroupPosition.x,
width: ensembleWidth,
paddingLeft,
paddingRight,
cache: null,
});
// Second format is to format the vexflow components, but don't draw them.
ensemble.format(fragmentRender, {
x: postPartLabelGroupPosition.x,
width: ensembleWidth,
paddingLeft,
paddingRight,
cache: throwawayFragmentRender,
});
const vexflowStaveConnectors = this.renderVexflowStaveConnectors(partRenders);
fragmentRender.vexflowStaveConnectors = vexflowStaveConnectors;
// After formatting, we can trust the y positions of the staves. Now we can render the part labels.
const partLabelGroupRender = this.renderPartLabelGroup(prePartLabelGroupPosition, partRenders);
if (partLabelGroupRender) {
fragmentRender.partLabelGroupRender = partLabelGroupRender;
fragmentRender.rect = spatial_1.Rect.merge([fragmentRender.rect, partLabelGroupRender.rect]);
}
const fragment = this.document.getFragment(this.key);
if (fragment.kind === 'nonmusical') {
fragmentRender.gapOverlay = new gapoverlay_1.GapOverlay(this.config, this.log, fragment.label, fragmentRender, fragment.style);
}
return fragmentRender;
}
accountForPartLabelGroupWidth(pen, widthBudget) {
if (!this.hasPartLabels()) {
return;
}
const partLabelGroup = new partlabelgroup_1.PartLabelGroup(this.config, this.log, this.document, this.key, pen.position(), null);
const partLabelGroupRender = partLabelGroup.render();
pen.moveBy({ dx: partLabelGroupRender.rect.w });
widthBudget.spend(partLabelGroupRender.rect.w);
}
renderPartLabelGroup(position, partRenders) {
if (!this.hasPartLabels()) {
return null;
}
const partLabelGroup = new partlabelgroup_1.PartLabelGroup(this.config, this.log, this.document, this.key, position, partRenders);
const partLabelGroupRender = partLabelGroup.render();
return partLabelGroupRender;
}
hasPartLabels() {
const isFirstSystem = this.document.isFirstSystem(this.key);
const isFirstMeasure = this.document.isFirstMeasure(this.key);
const isFirstFragment = this.document.isFirstFragment(this.key);
if (!isFirstSystem || !isFirstMeasure || !isFirstFragment) {
return false;
}
const partCount = this.document.getPartCount(this.key);
for (let partIndex = 0; partIndex < partCount; partIndex++) {
const key = { ...this.key, partIndex };
const partLabel = this.document.getPartLabel(key);
if (partLabel) {
return true;
}
}
return false;
}
renderParts(pen) {
const partRenders = new Array();
const partCount = this.document.getPartCount(this.key);
const isFirstMeasure = this.document.isFirstMeasure(this.key);
const isFirstFragment = this.document.isFirstFragment(this.key);
if (isFirstMeasure && isFirstFragment) {
pen.moveBy({ dx: MEASURE_NUMBER_PADDING_LEFT });
}
if (isFirstMeasure && isFirstFragment && this.hasBraceConnector()) {
pen.moveBy({ dx: BRACE_CONNECTOR_PADDING_LEFT });
}
for (let partIndex = 0; partIndex < partCount; partIndex++) {
const key = { ...this.key, partIndex };
const partRender = new part_1.Part(this.config, this.log, this.document, key, pen.position()).render();
partRenders.push(partRender);
const lastVexflowStave = partRender.staveRenders.at(-1)?.vexflowStave;
if (lastVexflowStave) {
pen.moveTo({ x: pen.x, y: lastVexflowStave.getBottomLineBottomY() });
}
pen.moveBy({ dy: this.config.PART_MARGIN_BOTTOM });
}
return partRenders;
}
renderVexflowStaveConnectors(partRenders) {
const vexflowStaveConnectors = new Array();
const staves = partRenders.flatMap((p) => p.staveRenders).map((s) => s.vexflowStave);
if (staves.length > 1) {
const firstVexflowStave = staves.at(0);
const lastVexflowStave = staves.at(-1);
const isFirstFragment = this.document.isFirstFragment(this.key);
if (isFirstFragment) {
vexflowStaveConnectors.push(new vexflow.StaveConnector(firstVexflowStave, lastVexflowStave).setType('singleLeft'));
}
const isLastFragment = this.document.isLastFragment(this.key);
const endBarlineStyle = this.document.getMeasure(this.key).endBarlineStyle;
if (isLastFragment) {
if (endBarlineStyle === 'double') {
vexflowStaveConnectors.push(new vexflow.StaveConnector(firstVexflowStave, lastVexflowStave).setType('thinDouble'));
}
else if (endBarlineStyle === 'end' || endBarlineStyle === 'repeatend') {
vexflowStaveConnectors.push(new vexflow.StaveConnector(firstVexflowStave, lastVexflowStave).setType('boldDoubleRight'));
}
else {
vexflowStaveConnectors.push(new vexflow.StaveConnector(firstVexflowStave, lastVexflowStave).setType('singleRight'));
}
}
}
return vexflowStaveConnectors;
}
hasBraceConnector() {
const partCount = this.document.getPartCount(this.key);
for (let partIndex = 0; partIndex < partCount; partIndex++) {
const key = { ...this.key, partIndex };
const staveCount = this.document.getStaveCount(key);
if (staveCount > 1) {
return true;
}
}
return false;
}
}
exports.Fragment = Fragment;