vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature.
170 lines (169 loc) • 6.31 kB
JavaScript
import { BoundingBox } from './boundingbox.js';
import { Glyph } from './glyph.js';
import { Note } from './note.js';
import { Stem } from './stem.js';
import { Tables } from './tables.js';
import { defined, log } from './util.js';
function L(...args) {
if (NoteHead.DEBUG)
log('Vex.Flow.NoteHead', args);
}
function drawSlashNoteHead(ctx, duration, x, y, stem_direction, staveSpace) {
const width = Tables.SLASH_NOTEHEAD_WIDTH;
ctx.save();
ctx.setLineWidth(Tables.STEM_WIDTH);
let fill = false;
if (Tables.durationToNumber(duration) > 2) {
fill = true;
}
if (!fill)
x -= (Tables.STEM_WIDTH / 2) * stem_direction;
ctx.beginPath();
ctx.moveTo(x, y + staveSpace);
ctx.lineTo(x, y + 1);
ctx.lineTo(x + width, y - staveSpace);
ctx.lineTo(x + width, y);
ctx.lineTo(x, y + staveSpace);
ctx.closePath();
if (fill) {
ctx.fill();
}
else {
ctx.stroke();
}
if (Tables.durationToFraction(duration).equals(0.5)) {
const breve_lines = [-3, -1, width + 1, width + 3];
for (let i = 0; i < breve_lines.length; i++) {
ctx.beginPath();
ctx.moveTo(x + breve_lines[i], y - 10);
ctx.lineTo(x + breve_lines[i], y + 11);
ctx.stroke();
}
}
ctx.restore();
}
class NoteHead extends Note {
static get CATEGORY() {
return "NoteHead";
}
constructor(noteStruct) {
super(noteStruct);
this.custom_glyph = false;
this.stem_up_x_offset = 0;
this.stem_down_x_offset = 0;
this.index = noteStruct.index;
this.x = noteStruct.x || 0;
this.y = noteStruct.y || 0;
if (noteStruct.note_type)
this.noteType = noteStruct.note_type;
this.displaced = noteStruct.displaced || false;
this.stem_direction = noteStruct.stem_direction || Stem.UP;
this.line = noteStruct.line || 0;
this.glyphProps = Tables.getGlyphProps(this.duration, this.noteType);
defined(this.glyphProps, 'BadArguments', `No glyph found for duration '${this.duration}' and type '${this.noteType}'`);
if ((this.line > 5 || this.line < 0) && this.glyphProps.ledger_code_head) {
this.glyphProps.code_head = this.glyphProps.ledger_code_head;
}
this.glyph_code = this.glyphProps.code_head;
this.x_shift = noteStruct.x_shift || 0;
if (noteStruct.custom_glyph_code) {
this.custom_glyph = true;
this.glyph_code = noteStruct.custom_glyph_code;
this.stem_up_x_offset = noteStruct.stem_up_x_offset || 0;
this.stem_down_x_offset = noteStruct.stem_down_x_offset || 0;
}
this.setStyle(noteStruct.style);
this.slashed = noteStruct.slashed || false;
this.render_options = Object.assign(Object.assign({}, this.render_options), { glyph_font_scale: noteStruct.glyph_font_scale || Tables.NOTATION_FONT_SCALE });
this.setWidth(this.custom_glyph &&
!this.glyph_code.startsWith('noteheadSlashed') &&
!this.glyph_code.startsWith('noteheadCircled')
? Glyph.getWidth(this.glyph_code, this.render_options.glyph_font_scale)
: this.glyphProps.getWidth(this.render_options.glyph_font_scale));
}
getWidth() {
return this.width;
}
isDisplaced() {
return this.displaced === true;
}
setX(x) {
this.x = x;
return this;
}
getY() {
return this.y;
}
setY(y) {
this.y = y;
return this;
}
getLine() {
return this.line;
}
setLine(line) {
this.line = line;
return this;
}
getAbsoluteX() {
const x = !this.preFormatted ? this.x : super.getAbsoluteX();
const displacementStemAdjustment = Stem.WIDTH / 2;
const musicFont = Tables.currentMusicFont();
const fontShift = musicFont.lookupMetric('notehead.shiftX', 0) * this.stem_direction;
const displacedFontShift = musicFont.lookupMetric('noteHead.displacedShiftX', 0) * this.stem_direction;
return (x +
fontShift +
(this.displaced ? (this.width - displacementStemAdjustment) * this.stem_direction + displacedFontShift : 0));
}
getBoundingBox() {
const spacing = this.checkStave().getSpacingBetweenLines();
const half_spacing = spacing / 2;
const min_y = this.y - half_spacing;
return new BoundingBox(this.getAbsoluteX(), min_y, this.width, spacing);
}
setStave(stave) {
const line = this.getLine();
this.stave = stave;
if (this.stave) {
this.setY(this.stave.getYForNote(line));
this.setContext(this.stave.getContext());
}
return this;
}
preFormat() {
if (this.preFormatted)
return this;
const width = this.getWidth() + this.leftDisplacedHeadPx + this.rightDisplacedHeadPx;
this.setWidth(width);
this.preFormatted = true;
return this;
}
draw() {
const ctx = this.checkContext();
this.setRendered();
let head_x = this.getAbsoluteX();
if (this.custom_glyph) {
head_x +=
this.stem_direction === Stem.UP
? this.stem_up_x_offset +
(this.glyphProps.stem ? this.glyphProps.getWidth(this.render_options.glyph_font_scale) - this.width : 0)
: this.stem_down_x_offset;
}
const y = this.y;
L("Drawing note head '", this.noteType, this.duration, "' at", head_x, y);
const stem_direction = this.stem_direction;
const glyph_font_scale = this.render_options.glyph_font_scale;
const categorySuffix = `${this.glyph_code}Stem${stem_direction === Stem.UP ? 'Up' : 'Down'}`;
if (this.noteType === 's') {
const staveSpace = this.checkStave().getSpacingBetweenLines();
drawSlashNoteHead(ctx, this.duration, head_x, y, stem_direction, staveSpace);
}
else {
Glyph.renderGlyph(ctx, head_x, y, glyph_font_scale, this.glyph_code, {
category: `noteHead.${categorySuffix}`,
});
}
}
}
NoteHead.DEBUG = false;
export { NoteHead };