UNPKG

@stringsync/vexml

Version:

MusicXML to Vexflow

254 lines (253 loc) 11.2 kB
"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;