vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature.
246 lines (245 loc) • 9.12 kB
JavaScript
import { Font, FontStyle, FontWeight } from './font.js';
import { Modifier, ModifierPosition } from './modifier.js';
import { Renderer } from './renderer.js';
import { Stem } from './stem.js';
import { Tables } from './tables.js';
import { isStaveNote, isStemmableNote } from './typeguard.js';
import { RuntimeError } from './util.js';
class StringNumber extends Modifier {
static get CATEGORY() {
return "StringNumber";
}
static get metrics() {
var _a;
return ((_a = Tables.currentMusicFont().getMetrics().stringNumber) !== null && _a !== void 0 ? _a : {
verticalPadding: 0,
stemPadding: 0,
leftPadding: 0,
rightPadding: 0,
});
}
static format(nums, state) {
const left_shift = state.left_shift;
const right_shift = state.right_shift;
const num_spacing = 1;
if (!nums || nums.length === 0)
return false;
const nums_list = [];
let prev_note = null;
let shift_left = 0;
let shift_right = 0;
const modLines = 0;
for (let i = 0; i < nums.length; ++i) {
const num = nums[i];
const note = num.getNote();
const pos = num.getPosition();
if (!isStaveNote(note)) {
throw new RuntimeError('NoStaveNote');
}
const index = num.checkIndex();
const props = note.getKeyProps()[index];
const mc = note.getModifierContext();
const verticalSpaceNeeded = (num.radius * 2) / Tables.STAVE_LINE_DISTANCE + 0.5;
if (mc) {
if (pos === ModifierPosition.ABOVE) {
num.text_line = mc.getState().top_text_line;
state.top_text_line += verticalSpaceNeeded;
}
else if (pos === ModifierPosition.BELOW) {
num.text_line = mc.getState().text_line;
state.text_line += verticalSpaceNeeded;
}
}
if (note !== prev_note) {
for (let n = 0; n < note.keys.length; ++n) {
if (left_shift === 0) {
shift_left = Math.max(note.getLeftDisplacedHeadPx(), shift_left);
}
if (right_shift === 0) {
shift_right = Math.max(note.getRightDisplacedHeadPx(), shift_right);
}
}
prev_note = note;
}
const glyphLine = modLines === 0 ? props.line : modLines;
nums_list.push({
pos,
note,
num,
line: glyphLine,
shiftL: shift_left,
shiftR: shift_right,
});
}
nums_list.sort((a, b) => b.line - a.line);
let num_shiftR = 0;
let x_widthL = 0;
let x_widthR = 0;
let last_line = null;
let last_note = null;
for (let i = 0; i < nums_list.length; ++i) {
let num_shift = 0;
const note = nums_list[i].note;
const pos = nums_list[i].pos;
const num = nums_list[i].num;
const line = nums_list[i].line;
const shiftR = nums_list[i].shiftR;
if (line !== last_line || note !== last_note) {
num_shiftR = right_shift + shiftR;
}
const num_width = num.getWidth() + num_spacing;
if (pos === Modifier.Position.LEFT) {
num.setXShift(left_shift);
num_shift = shift_left + num_width;
x_widthL = num_shift > x_widthL ? num_shift : x_widthL;
}
else if (pos === Modifier.Position.RIGHT) {
num.setXShift(num_shiftR);
num_shift += num_width;
x_widthR = num_shift > x_widthR ? num_shift : x_widthR;
}
last_line = line;
last_note = note;
}
state.left_shift += x_widthL;
state.right_shift += x_widthR;
return true;
}
constructor(number) {
super();
this.string_number = number;
this.position = Modifier.Position.ABOVE;
this.x_shift = 0;
this.y_shift = 0;
this.text_line = 0;
this.stem_offset = 0;
this.x_offset = 0;
this.y_offset = 0;
this.dashed = true;
this.leg = Renderer.LineEndType.NONE;
this.radius = 8;
this.drawCircle = true;
this.setWidth(this.radius * 2 + 4);
this.resetFont();
}
setLineEndType(leg) {
if (leg >= Renderer.LineEndType.NONE && leg <= Renderer.LineEndType.DOWN) {
this.leg = leg;
}
return this;
}
setStringNumber(number) {
this.string_number = number;
return this;
}
setOffsetX(x) {
this.x_offset = x;
return this;
}
setOffsetY(y) {
this.y_offset = y;
return this;
}
setLastNote(note) {
this.last_note = note;
return this;
}
setDashed(dashed) {
this.dashed = dashed;
return this;
}
setDrawCircle(drawCircle) {
this.drawCircle = drawCircle;
return this;
}
draw() {
const ctx = this.checkContext();
const note = this.checkAttachedNote();
this.setRendered();
const start = note.getModifierStartXY(this.position, this.index);
const stemDirection = note.hasStem() ? note.getStemDirection() : Stem.UP;
let dot_x = start.x + this.x_shift + this.x_offset;
let stem_ext = {};
if (note.hasStem()) {
stem_ext = note.checkStem().getExtents();
}
let dot_y = start.y + this.y_shift + this.y_offset;
switch (this.position) {
case Modifier.Position.ABOVE:
{
const ys = note.getYs();
dot_y = ys.reduce((a, b) => (a < b ? a : b));
if (note.hasStem() && stemDirection == Stem.UP) {
dot_y = stem_ext.topY + StringNumber.metrics.stemPadding;
}
dot_y -= this.radius + StringNumber.metrics.verticalPadding + this.text_line * Tables.STAVE_LINE_DISTANCE;
}
break;
case Modifier.Position.BELOW:
{
const ys = note.getYs();
dot_y = ys.reduce((a, b) => (a > b ? a : b));
if (note.hasStem() && stemDirection == Stem.DOWN) {
dot_y = stem_ext.topY - StringNumber.metrics.stemPadding;
}
dot_y += this.radius + StringNumber.metrics.verticalPadding + this.text_line * Tables.STAVE_LINE_DISTANCE;
}
break;
case Modifier.Position.LEFT:
dot_x -= this.radius / 2 + StringNumber.metrics.leftPadding;
break;
case Modifier.Position.RIGHT:
dot_x += this.radius / 2 + StringNumber.metrics.rightPadding;
break;
default:
throw new RuntimeError('InvalidPosition', `The position ${this.position} is invalid`);
}
ctx.save();
if (this.drawCircle) {
ctx.beginPath();
ctx.arc(dot_x, dot_y, this.radius, 0, Math.PI * 2, false);
ctx.setLineWidth(1.5);
ctx.stroke();
}
ctx.setFont(this.textFont);
const x = dot_x - ctx.measureText(this.string_number).width / 2;
ctx.fillText('' + this.string_number, x, dot_y + 4.5);
const lastNote = this.last_note;
if (isStemmableNote(lastNote)) {
const end = lastNote.getStemX() - note.getX() + 5;
ctx.setStrokeStyle('#000000');
ctx.setLineCap('round');
ctx.setLineWidth(0.6);
if (this.dashed) {
Renderer.drawDashedLine(ctx, dot_x + 10, dot_y, dot_x + end, dot_y, [3, 3]);
}
else {
Renderer.drawDashedLine(ctx, dot_x + 10, dot_y, dot_x + end, dot_y, [3, 0]);
}
let len;
let pattern;
switch (this.leg) {
case Renderer.LineEndType.UP:
len = -10;
pattern = this.dashed ? [3, 3] : [3, 0];
Renderer.drawDashedLine(ctx, dot_x + end, dot_y, dot_x + end, dot_y + len, pattern);
break;
case Renderer.LineEndType.DOWN:
len = 10;
pattern = this.dashed ? [3, 3] : [3, 0];
Renderer.drawDashedLine(ctx, dot_x + end, dot_y, dot_x + end, dot_y + len, pattern);
break;
default:
break;
}
}
ctx.restore();
}
}
StringNumber.TEXT_FONT = {
family: Font.SANS_SERIF,
size: Font.SIZE,
weight: FontWeight.BOLD,
style: FontStyle.NORMAL,
};
export { StringNumber };