VexFlow - Music Engraving for HTML5 Copyright Mohit Muthanna 2010
This class implements curves (for slurs)
import { Vex } from './vex';
import { Element } from './element';
export class Curve extends Element {
static get Position() {
return {
NEAR_HEAD: 1,
NEAR_TOP: 2,
};
}
from: Start note to: End note options: cps: List of control points x_shift: pixels to shift y_shift: pixels to shift
constructor(from, to, options) {
super();
this.attrs.type = 'Curve';
this.render_options = {
spacing: 2,
thickness: 2,
x_shift: 0,
y_shift: 10,
position: Curve.Position.NEAR_HEAD,
invert: false,
cps: [{ x: 0, y: 10 }, { x: 0, y: 10 }],
};
Vex.Merge(this.render_options, options);
this.setNotes(from, to);
}
setNotes(from, to) {
if (!from && !to) {
throw new Vex.RuntimeError(
'BadArguments', 'Curve needs to have either first_note or last_note set.'
);
}
this.from = from;
this.to = to;
return this;
}
/**
* @return {boolean} Returns true if this is a partial bar.
*/
isPartial() {
return (!this.from || !this.to);
}
renderCurve(params) {
const ctx = this.context;
const cps = this.render_options.cps;
const x_shift = this.render_options.x_shift;
const y_shift = this.render_options.y_shift * params.direction;
const first_x = params.first_x + x_shift;
const first_y = params.first_y + y_shift;
const last_x = params.last_x - x_shift;
const last_y = params.last_y + y_shift;
const thickness = this.render_options.thickness;
const cp_spacing = (last_x - first_x) / (cps.length + 2);
ctx.beginPath();
ctx.moveTo(first_x, first_y);
ctx.bezierCurveTo(
first_x + cp_spacing + cps[0].x,
first_y + (cps[0].y * params.direction),
last_x - cp_spacing + cps[1].x,
last_y + (cps[1].y * params.direction),
last_x,
last_y
);
ctx.bezierCurveTo(
last_x - cp_spacing + cps[1].x,
last_y + ((cps[1].y + thickness) * params.direction),
first_x + cp_spacing + cps[0].x,
first_y + ((cps[0].y + thickness) * params.direction),
first_x,
first_y
);
ctx.stroke();
ctx.closePath();
ctx.fill();
}
draw() {
this.checkContext();
const first_note = this.from;
const last_note = this.to;
let first_x;
let last_x;
let first_y;
let last_y;
let stem_direction;
let metric = 'baseY';
let end_metric = 'baseY';
const position = this.render_options.position;
const position_end = this.render_options.position_end;
if (position === Curve.Position.NEAR_TOP) {
metric = 'topY';
end_metric = 'topY';
}
if (position_end === Curve.Position.NEAR_HEAD) {
end_metric = 'baseY';
} else if (position_end === Curve.Position.NEAR_TOP) {
end_metric = 'topY';
}
if (first_note) {
first_x = first_note.getTieRightX();
stem_direction = first_note.getStemDirection();
first_y = first_note.getStemExtents()[metric];
} else {
first_x = last_note.getStave().getTieStartX();
first_y = last_note.getStemExtents()[metric];
}
if (last_note) {
last_x = last_note.getTieLeftX();
stem_direction = last_note.getStemDirection();
last_y = last_note.getStemExtents()[end_metric];
} else {
last_x = first_note.getStave().getTieEndX();
last_y = first_note.getStemExtents()[end_metric];
}
this.renderCurve({
first_x,
last_x,
first_y,
last_y,
direction: stem_direction * (this.render_options.invert === true ? -1 : 1),
});
return true;
}
}