|
|
|
|
|
|
|
|
|
|
|
|
|
|
var spacing = require('./helpers/spacing'); |
|
|
var AbstractEngraver = require('./creation/abstract-engraver'); |
|
|
var Renderer = require('./renderer'); |
|
|
var FreeText = require('./creation/elements/free-text'); |
|
|
var Separator = require('./creation/elements/separator'); |
|
|
var Subtitle = require('./creation/elements/subtitle'); |
|
|
var TopText = require('./creation/elements/top-text'); |
|
|
var BottomText = require('./creation/elements/bottom-text'); |
|
|
var setupSelection = require('./interactive/selection'); |
|
|
var layout = require('./layout/layout'); |
|
|
var Classes = require('./helpers/classes'); |
|
|
var GetFontAndAttr = require('./helpers/get-font-and-attr'); |
|
|
var GetTextSize = require('./helpers/get-text-size'); |
|
|
var draw = require('./draw/draw'); |
|
|
var tablatures = require('../tablatures/abc_tablatures'); |
|
|
var findSelectableElement = require('./interactive/find-selectable-element'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var EngraverController = function (paper, params) { |
|
|
params = params || {}; |
|
|
this.findSelectableElement = findSelectableElement; |
|
|
this.oneSvgPerLine = params.oneSvgPerLine; |
|
|
this.selectionColor = params.selectionColor; |
|
|
this.dragColor = params.dragColor ? params.dragColor : params.selectionColor; |
|
|
this.dragging = !!params.dragging; |
|
|
this.selectTypes = params.selectTypes; |
|
|
this.responsive = params.responsive; |
|
|
this.space = 3 * spacing.SPACE; |
|
|
this.initialClef = params.initialClef; |
|
|
this.timeBasedLayout = params.timeBasedLayout; |
|
|
this.expandToWidest = !!params.expandToWidest; |
|
|
this.scale = params.scale ? parseFloat(params.scale) : 0; |
|
|
this.classes = new Classes({ shouldAddClasses: params.add_classes }); |
|
|
if (!(this.scale > 0.1)) |
|
|
this.scale = undefined; |
|
|
|
|
|
if (params.staffwidth) { |
|
|
|
|
|
|
|
|
this.staffwidthScreen = params.staffwidth; |
|
|
this.staffwidthPrint = params.staffwidth; |
|
|
} else { |
|
|
this.staffwidthScreen = 740; |
|
|
this.staffwidthPrint = 680; |
|
|
} |
|
|
this.listeners = []; |
|
|
if (params.clickListener) |
|
|
this.addSelectListener(params.clickListener); |
|
|
|
|
|
this.renderer = new Renderer(paper); |
|
|
this.renderer.setPaddingOverride(params); |
|
|
if (params.showDebug) |
|
|
this.renderer.showDebug = params.showDebug; |
|
|
if (params.jazzchords) |
|
|
this.jazzchords = params.jazzchords; |
|
|
if (params.accentAbove) |
|
|
this.accentAbove = params.accentAbove; |
|
|
if (params.germanAlphabet) |
|
|
this.germanAlphabet = params.germanAlphabet; |
|
|
if (params.lineThickness) |
|
|
this.lineThickness = params.lineThickness; |
|
|
this.renderer.controller = this; |
|
|
this.renderer.foregroundColor = params.foregroundColor ? params.foregroundColor : "currentColor"; |
|
|
if (params.ariaLabel !== undefined) |
|
|
this.renderer.ariaLabel = params.ariaLabel; |
|
|
this.renderer.minPadding = params.minPadding ? params.minPadding : 0; |
|
|
|
|
|
this.reset(); |
|
|
}; |
|
|
|
|
|
EngraverController.prototype.reset = function () { |
|
|
this.selected = []; |
|
|
this.staffgroups = []; |
|
|
if (this.engraver) |
|
|
this.engraver.reset(); |
|
|
this.engraver = null; |
|
|
this.renderer.reset(); |
|
|
this.dragTarget = null; |
|
|
this.dragIndex = -1; |
|
|
this.dragMouseStart = { x: -1, y: -1 }; |
|
|
this.dragYStep = 0; |
|
|
if (this.lineThickness) |
|
|
this.renderer.setLineThickness(this.lineThickness) |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
EngraverController.prototype.engraveABC = function (abctunes, tuneNumber, lineOffset) { |
|
|
if (abctunes[0] === undefined) { |
|
|
abctunes = [abctunes]; |
|
|
} |
|
|
this.reset(); |
|
|
|
|
|
for (var i = 0; i < abctunes.length; i++) { |
|
|
if (tuneNumber === undefined) |
|
|
tuneNumber = i; |
|
|
this.getFontAndAttr = new GetFontAndAttr(abctunes[i].formatting, this.classes); |
|
|
this.getTextSize = new GetTextSize(this.getFontAndAttr, this.renderer.paper); |
|
|
this.engraveTune(abctunes[i], tuneNumber, lineOffset); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
EngraverController.prototype.adjustNonScaledItems = function (scale) { |
|
|
this.width /= scale; |
|
|
this.renderer.adjustNonScaledItems(scale); |
|
|
}; |
|
|
|
|
|
EngraverController.prototype.getMeasureWidths = function (abcTune) { |
|
|
this.reset(); |
|
|
this.getFontAndAttr = new GetFontAndAttr(abcTune.formatting, this.classes); |
|
|
this.getTextSize = new GetTextSize(this.getFontAndAttr, this.renderer.paper); |
|
|
var origJazzChords = this.jazzchords |
|
|
|
|
|
this.setupTune(abcTune, 0); |
|
|
this.constructTuneElements(abcTune); |
|
|
|
|
|
|
|
|
layout(this.renderer, abcTune, 0, this.space, this.timeBasedLayout); |
|
|
|
|
|
var ret = []; |
|
|
var section; |
|
|
|
|
|
var needNewSection = true; |
|
|
for (var i = 0; i < abcTune.lines.length; i++) { |
|
|
var abcLine = abcTune.lines[i]; |
|
|
if (abcLine.staff) { |
|
|
if (needNewSection) { |
|
|
section = { |
|
|
left: 0, |
|
|
measureWidths: [], |
|
|
|
|
|
total: 0 |
|
|
}; |
|
|
ret.push(section); |
|
|
needNewSection = false; |
|
|
} |
|
|
|
|
|
if (abcLine.staffGroup.voices.length > 0) { |
|
|
var voice = abcLine.staffGroup.voices[0]; |
|
|
var foundNotStaffExtra = false; |
|
|
var lastXPosition = 0; |
|
|
for (var k = 0; k < voice.children.length; k++) { |
|
|
var child = voice.children[k]; |
|
|
if (!foundNotStaffExtra && !child.isClef && !child.isKeySig) { |
|
|
foundNotStaffExtra = true; |
|
|
section.left = child.x; |
|
|
lastXPosition = child.x; |
|
|
} |
|
|
if (child.type === 'bar') { |
|
|
section.measureWidths.push(child.x - lastXPosition); |
|
|
section.total += (child.x - lastXPosition); |
|
|
lastXPosition = child.x; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
} else |
|
|
needNewSection = true; |
|
|
} |
|
|
this.jazzchords = origJazzChords |
|
|
return ret; |
|
|
}; |
|
|
|
|
|
EngraverController.prototype.setupTune = function (abcTune, tuneNumber) { |
|
|
this.classes.reset(); |
|
|
|
|
|
if (abcTune.formatting.jazzchords !== undefined) |
|
|
this.jazzchords = abcTune.formatting.jazzchords; |
|
|
if (abcTune.formatting.accentAbove !== undefined) |
|
|
this.accentAbove = abcTune.formatting.accentAbove; |
|
|
|
|
|
this.renderer.newTune(abcTune); |
|
|
this.engraver = new AbstractEngraver(this.getTextSize, tuneNumber, { |
|
|
bagpipes: abcTune.formatting.bagpipes, |
|
|
flatbeams: abcTune.formatting.flatbeams, |
|
|
graceSlurs: abcTune.formatting.graceSlurs !== false, |
|
|
percmap: abcTune.formatting.percmap, |
|
|
initialClef: this.initialClef, |
|
|
jazzchords: this.jazzchords, |
|
|
timeBasedLayout: this.timeBasedLayout, |
|
|
accentAbove: this.accentAbove, |
|
|
germanAlphabet: this.germanAlphabet |
|
|
}); |
|
|
this.engraver.setStemHeight(this.renderer.spacing.stemHeight); |
|
|
this.engraver.measureLength = abcTune.getMeterFraction().num / abcTune.getMeterFraction().den; |
|
|
if (abcTune.formatting.staffwidth) { |
|
|
this.width = abcTune.formatting.staffwidth * 1.33; |
|
|
} else { |
|
|
this.width = this.renderer.isPrint ? this.staffwidthPrint : this.staffwidthScreen; |
|
|
} |
|
|
|
|
|
var scale = abcTune.formatting.scale ? abcTune.formatting.scale : this.scale; |
|
|
if (this.responsive === "resize") |
|
|
scale = undefined; |
|
|
if (scale === undefined) scale = this.renderer.isPrint ? 0.75 : 1; |
|
|
this.adjustNonScaledItems(scale); |
|
|
return scale; |
|
|
}; |
|
|
|
|
|
EngraverController.prototype.constructTuneElements = function (abcTune) { |
|
|
abcTune.topText = new TopText(abcTune.metaText, abcTune.metaTextInfo, abcTune.formatting, abcTune.lines, this.width, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.classes.shouldAddClasses, this.getTextSize); |
|
|
|
|
|
|
|
|
var i; |
|
|
var abcLine; |
|
|
var hasPrintedTempo = false; |
|
|
var hasSeenNonSubtitle = false; |
|
|
for (i = 0; i < abcTune.lines.length; i++) { |
|
|
abcLine = abcTune.lines[i]; |
|
|
if (abcLine.staff) { |
|
|
hasSeenNonSubtitle = true; |
|
|
abcLine.staffGroup = this.engraver.createABCLine(abcLine.staff, !hasPrintedTempo ? abcTune.metaText.tempo : null, i); |
|
|
hasPrintedTempo = true; |
|
|
} else if (abcLine.subtitle) { |
|
|
|
|
|
if (hasSeenNonSubtitle) { |
|
|
var center = this.width / 2 + this.renderer.padding.left; |
|
|
abcLine.nonMusic = new Subtitle(this.renderer.spacing.subtitle, abcTune.formatting, abcLine.subtitle, center, this.renderer.padding.left, this.getTextSize); |
|
|
} |
|
|
} else if (abcLine.text !== undefined) { |
|
|
hasSeenNonSubtitle = true; |
|
|
abcLine.nonMusic = new FreeText(abcLine.text, abcLine.vskip, this.getFontAndAttr, this.renderer.padding.left, this.width, this.getTextSize); |
|
|
} else if (abcLine.separator !== undefined && abcLine.separator.lineLength) { |
|
|
hasSeenNonSubtitle = true; |
|
|
abcLine.nonMusic = new Separator(abcLine.separator.spaceAbove, abcLine.separator.lineLength, abcLine.separator.spaceBelow); |
|
|
} |
|
|
} |
|
|
abcTune.bottomText = new BottomText(abcTune.metaText, this.width, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.classes.shouldAddClasses, this.getTextSize); |
|
|
}; |
|
|
|
|
|
EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOffset) { |
|
|
|
|
|
var origJazzChords = this.jazzchords |
|
|
var scale = this.setupTune(abcTune, tuneNumber); |
|
|
|
|
|
|
|
|
this.constructTuneElements(abcTune); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest, this.timeBasedLayout); |
|
|
|
|
|
|
|
|
if (this.expandToWidest && maxWidth > this.width + 1) { |
|
|
|
|
|
abcTune.topText = new TopText(abcTune.metaText, abcTune.metaTextInfo, abcTune.formatting, abcTune.lines, maxWidth, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.classes.shouldAddClasses, this.getTextSize); |
|
|
|
|
|
if ((abcTune.lines)&&(abcTune.lines.length > 0)){ |
|
|
var nlines = abcTune.lines.length; |
|
|
|
|
|
for (var i=0;i<nlines;++i){ |
|
|
var entry = abcTune.lines[i]; |
|
|
if (entry.nonMusic){ |
|
|
if ((entry.nonMusic.rows) && (entry.nonMusic.rows.length > 0)){ |
|
|
var nRows = entry.nonMusic.rows.length; |
|
|
for (var j=0;j<nRows;++j){ |
|
|
var thisRow = entry.nonMusic.rows[j]; |
|
|
|
|
|
if (thisRow.left){ |
|
|
if (entry.subtitle){ |
|
|
thisRow.left = (maxWidth/2) + this.renderer.padding.left; |
|
|
} else { |
|
|
if ((entry.text)&&(entry.text.length>0)){ |
|
|
if (entry.text[0].center){ |
|
|
thisRow.left = (maxWidth/2) + this.renderer.padding.left; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (abcTune.tablatures) { |
|
|
tablatures.layoutTablatures(this.renderer, abcTune); |
|
|
} |
|
|
|
|
|
|
|
|
var ret = draw(this.renderer, this.classes, abcTune, this.width, maxWidth, this.responsive, scale, this.selectTypes, tuneNumber, lineOffset); |
|
|
this.staffgroups = ret.staffgroups; |
|
|
this.selectables = ret.selectables; |
|
|
if (this.oneSvgPerLine) { |
|
|
var div = this.renderer.paper.svg.parentNode; |
|
|
this.svgs = splitSvgIntoLines(this.renderer, div, abcTune.metaText.title, this.responsive, scale); |
|
|
} else { |
|
|
this.svgs = [this.renderer.paper.svg]; |
|
|
} |
|
|
setupSelection(this, this.svgs); |
|
|
|
|
|
this.jazzchords = origJazzChords |
|
|
}; |
|
|
|
|
|
function splitSvgIntoLines(renderer, output, title, responsive, scale) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!title) title = "Untitled" |
|
|
var source = output.querySelector("svg") |
|
|
if (responsive === 'resize') |
|
|
output.style.paddingBottom = '' |
|
|
var style = source.querySelector("style") |
|
|
var width = responsive === 'resize' ? source.viewBox.baseVal.width : source.getAttribute("width") |
|
|
var sections = output.querySelectorAll("svg > g") |
|
|
var nextTop = 0 |
|
|
var wrappers = [] |
|
|
var svgs = [] |
|
|
for (var i = 0; i < sections.length; i++) { |
|
|
var section = sections[i] |
|
|
var box = section.getBBox() |
|
|
var gapBetweenLines = box.y - nextTop |
|
|
var height = box.height + gapBetweenLines; |
|
|
var wrapper = document.createElement("div"); |
|
|
var divStyles = "overflow: hidden;" |
|
|
if (responsive !== 'resize') |
|
|
divStyles += "height:" + (height * scale) + "px;" |
|
|
wrapper.setAttribute("style", divStyles) |
|
|
var svg = duplicateSvg(source) |
|
|
var fullTitle = "Sheet Music for \"" + title + "\" section " + (i + 1) |
|
|
svg.setAttribute("aria-label", fullTitle) |
|
|
if (responsive !== 'resize') |
|
|
svg.setAttribute("height", height) |
|
|
if (responsive === 'resize') |
|
|
svg.style.position = '' |
|
|
|
|
|
var viewBoxHeight = renderer.firefox112 ? height+1 : height |
|
|
svg.setAttribute("viewBox", "0 " + nextTop + " " + width + " " + viewBoxHeight) |
|
|
svg.appendChild(style.cloneNode(true)) |
|
|
var titleEl = document.createElement("title") |
|
|
titleEl.innerText = fullTitle |
|
|
svg.appendChild(titleEl) |
|
|
svg.appendChild(section) |
|
|
|
|
|
wrapper.appendChild(svg) |
|
|
svgs.push(svg) |
|
|
output.appendChild(wrapper) |
|
|
|
|
|
nextTop = box.y + box.height |
|
|
} |
|
|
|
|
|
|
|
|
output.removeChild(source) |
|
|
return svgs; |
|
|
} |
|
|
|
|
|
function duplicateSvg(source) { |
|
|
var svgNS = "http://www.w3.org/2000/svg"; |
|
|
var svg = document.createElementNS(svgNS, "svg"); |
|
|
for (var i = 0; i < source.attributes.length; i++) { |
|
|
var attr = source.attributes[i]; |
|
|
if (attr.name !== "height" && attr.name != "aria-label") |
|
|
svg.setAttribute(attr.name, attr.value) |
|
|
} |
|
|
return svg; |
|
|
} |
|
|
|
|
|
EngraverController.prototype.getDim = function (historyEl) { |
|
|
|
|
|
if (!historyEl.dim) { |
|
|
var box = historyEl.svgEl.getBBox(); |
|
|
historyEl.dim = { left: Math.round(box.x), top: Math.round(box.y), right: Math.round(box.x + box.width), bottom: Math.round(box.y + box.height) }; |
|
|
} |
|
|
return historyEl.dim; |
|
|
}; |
|
|
|
|
|
EngraverController.prototype.addSelectListener = function (clickListener) { |
|
|
this.listeners[this.listeners.length] = clickListener; |
|
|
}; |
|
|
|
|
|
module.exports = EngraverController; |
|
|
|