UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature

209 lines (181 loc) 6.43 kB
// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010. // // ## Description // // This file implements text annotations as modifiers that can be attached to // notes. // // See `tests/annotation_tests.js` for usage examples. import { Vex } from './vex'; import { Flow } from './tables'; import { Modifier } from './modifier'; // To enable logging for this class. Set `Vex.Flow.Annotation.DEBUG` to `true`. function L(...args) { if (Annotation.DEBUG) Vex.L('Vex.Flow.Annotation', args); } export class Annotation extends Modifier { static get CATEGORY() { return 'annotations'; } // Text annotations can be positioned and justified relative to the note. static get Justify() { return { LEFT: 1, CENTER: 2, RIGHT: 3, CENTER_STEM: 4, }; } static get JustifyString() { return { left: Annotation.Justify.LEFT, right: Annotation.Justify.RIGHT, center: Annotation.Justify.CENTER, centerStem: Annotation.Justify.CENTER_STEM, }; } static get VerticalJustify() { return { TOP: 1, CENTER: 2, BOTTOM: 3, CENTER_STEM: 4, }; } static get VerticalJustifyString() { return { above: Annotation.VerticalJustify.TOP, top: Annotation.VerticalJustify.TOP, below: Annotation.VerticalJustify.BOTTOM, bottom: Annotation.VerticalJustify.BOTTOM, center: Annotation.VerticalJustify.CENTER, centerStem: Annotation.VerticalJustify.CENTER_STEM, }; } // Arrange annotations within a `ModifierContext` static format(annotations, state) { if (!annotations || annotations.length === 0) return false; let width = 0; for (let i = 0; i < annotations.length; ++i) { const annotation = annotations[i]; width = Math.max(annotation.getWidth(), width); if (annotation.getPosition() === Modifier.Position.ABOVE) { annotation.setTextLine(state.top_text_line); state.top_text_line++; } else { annotation.setTextLine(state.text_line); state.text_line++; } } state.left_shift += width / 2; state.right_shift += width / 2; return true; } // ## Prototype Methods // // Annotations inherit from `Modifier` and is positioned correctly when // in a `ModifierContext`. // Create a new `Annotation` with the string `text`. constructor(text) { super(); this.setAttribute('type', 'Annotation'); this.note = null; this.index = null; this.text = text; this.justification = Annotation.Justify.CENTER; this.vert_justification = Annotation.VerticalJustify.TOP; this.font = { family: 'Arial', size: 10, weight: '', }; // The default width is calculated from the text. this.setWidth(Flow.textWidth(text)); } getCategory() { return Annotation.CATEGORY; } // Set font family, size, and weight. E.g., `Arial`, `10pt`, `Bold`. setFont(family, size, weight) { this.font = { family, size, weight }; return this; } // Set vertical position of text (above or below stave). `just` must be // a value in `Annotation.VerticalJustify`. setVerticalJustification(just) { this.vert_justification = typeof (just) === 'string' ? Annotation.VerticalJustifyString[just] : just; return this; } // Get and set horizontal justification. `justification` is a value in // `Annotation.Justify`. getJustification() { return this.justification; } setJustification(just) { this.justification = typeof (just) === 'string' ? Annotation.JustifyString[just] : just; return this; } // Render text beside the note. draw() { this.checkContext(); if (!this.note) { throw new Vex.RERR( 'NoNoteForAnnotation', "Can't draw text annotation without an attached note." ); } this.setRendered(); const start = this.note.getModifierStartXY(Modifier.Position.ABOVE, this.index); // We're changing context parameters. Save current state. this.context.save(); this.context.setFont(this.font.family, this.font.size, this.font.weight); const text_width = this.context.measureText(this.text).width; // Estimate text height to be the same as the width of an 'm'. // // This is a hack to work around the inability to measure text height // in HTML5 Canvas (and SVG). const text_height = this.context.measureText('m').width; let x; let y; if (this.justification === Annotation.Justify.LEFT) { x = start.x; } else if (this.justification === Annotation.Justify.RIGHT) { x = start.x - text_width; } else if (this.justification === Annotation.Justify.CENTER) { x = start.x - text_width / 2; } else /* CENTER_STEM */ { x = this.note.getStemX() - text_width / 2; } let stem_ext; let spacing; const has_stem = this.note.hasStem(); const stave = this.note.getStave(); // The position of the text varies based on whether or not the note // has a stem. if (has_stem) { stem_ext = this.note.getStem().getExtents(); spacing = stave.getSpacingBetweenLines(); } if (this.vert_justification === Annotation.VerticalJustify.BOTTOM) { // HACK: We need to compensate for the text's height since its origin // is bottom-right. y = stave.getYForBottomText(this.text_line + Flow.TEXT_HEIGHT_OFFSET_HACK); if (has_stem) { const stem_base = (this.note.getStemDirection() === 1 ? stem_ext.baseY : stem_ext.topY); y = Math.max(y, stem_base + (spacing * (this.text_line + 2))); } } else if (this.vert_justification === Annotation.VerticalJustify.CENTER) { const yt = this.note.getYForTopText(this.text_line) - 1; const yb = stave.getYForBottomText(this.text_line); y = yt + (yb - yt) / 2 + text_height / 2; } else if (this.vert_justification === Annotation.VerticalJustify.TOP) { y = Math.min(stave.getYForTopText(this.text_line), this.note.getYs()[0] - 10); if (has_stem) { y = Math.min(y, (stem_ext.topY - 5) - (spacing * this.text_line)); } } else /* CENTER_STEM */ { const extents = this.note.getStemExtents(); y = extents.topY + (extents.baseY - extents.topY) / 2 + text_height / 2; } L('Rendering annotation: ', this.text, x, y); this.context.fillText(this.text, x, y); this.context.restore(); } }