vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature.
254 lines (210 loc) • 7.21 kB
text/typescript
// [VexFlow](https://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
// Author: Cyril Silverman
//
// This file implements `TextBrackets` which extend between two notes.
// The octave transposition markings (8va, 8vb, 15va, 15vb) can be created
// using this class.
import { Element } from './element';
import { Font, FontInfo, FontStyle, FontWeight } from './font';
import { Note } from './note';
import { RenderContext } from './rendercontext';
import { Renderer } from './renderer';
import { Tables } from './tables';
import { Category } from './typeguard';
import { log, RuntimeError } from './util';
export interface TextBracketParams {
start: Note;
stop: Note;
text?: string;
superscript?: string;
position?: number | string;
}
// To enable logging for this class. Set `Vex.Flow.TextBracket.DEBUG` to `true`.
// eslint-disable-next-line
function L(...args: any[]) {
if (TextBracket.DEBUG) log('Vex.Flow.TextBracket', args);
}
export enum TextBracketPosition {
TOP = 1,
BOTTOM = -1,
}
export class TextBracket extends Element {
static DEBUG: boolean = false;
static get CATEGORY(): string {
return Category.TextBracket;
}
static TEXT_FONT: Required<FontInfo> = {
family: Font.SERIF,
size: 15,
weight: FontWeight.NORMAL,
style: FontStyle.ITALIC,
};
public render_options: {
dashed: boolean;
color: string;
line_width: number;
underline_superscript: boolean;
show_bracket: boolean;
dash: number[];
bracket_height: number;
};
protected readonly text: string;
protected readonly superscript: string;
protected line: number;
readonly position: TextBracketPosition;
readonly start: Note;
readonly stop: Note;
static get Position(): typeof TextBracketPosition {
return TextBracketPosition;
}
static get PositionString(): Record<string, number> {
return {
top: TextBracketPosition.TOP,
bottom: TextBracketPosition.BOTTOM,
};
}
/**
* @deprecated Use `TextBracket.Position` instead.
*/
static get Positions(): typeof TextBracketPosition {
L('Positions is deprecated, use TextBracketPosition instead.');
return TextBracketPosition;
}
/**
* @deprecated Use `TextBracket.PositionString` instead.
*/
static get PositionsString(): Record<string, number> {
L('PositionsString is deprecated, use PositionString instead.');
return TextBracket.PositionString;
}
constructor({ start, stop, text = '', superscript = '', position = TextBracketPosition.TOP }: TextBracketParams) {
super();
this.start = start;
this.stop = stop;
this.text = text;
this.superscript = superscript;
this.position = typeof position === 'string' ? TextBracket.PositionString[position] : position;
this.line = 1;
this.resetFont();
this.render_options = {
dashed: true,
dash: [5],
color: 'black',
line_width: 1,
show_bracket: true,
bracket_height: 8,
// In the BOTTOM position, the bracket line can extend
// under the superscript.
underline_superscript: true,
};
}
/**
* Apply the text backet styling to the provided context.
* @param ctx
* @returns this
*/
applyStyle(ctx: RenderContext): this {
ctx.setFont(this.font);
const options = this.render_options;
ctx.setStrokeStyle(options.color);
ctx.setFillStyle(options.color);
ctx.setLineWidth(options.line_width);
return this;
}
// Set whether the bracket line should be `dashed`. You can also
// optionally set the `dash` pattern by passing in an array of numbers
setDashed(dashed: boolean, dash?: number[]): this {
this.render_options.dashed = dashed;
if (dash) this.render_options.dash = dash;
return this;
}
// Set the rendering `context` for the octave bracket
setLine(line: number): this {
this.line = line;
return this;
}
// Draw the octave bracket on the rendering context
draw(): void {
const ctx = this.checkContext();
this.setRendered();
let y = 0;
switch (this.position) {
case TextBracketPosition.TOP:
y = this.start.checkStave().getYForTopText(this.line);
break;
case TextBracketPosition.BOTTOM:
y = this.start.checkStave().getYForBottomText(this.line + Tables.TEXT_HEIGHT_OFFSET_HACK);
break;
default:
throw new RuntimeError('InvalidPosition', `The position ${this.position} is invalid.`);
}
// Get the preliminary start and stop coordintates for the bracket
const start = { x: this.start.getAbsoluteX(), y };
const stop = { x: this.stop.getAbsoluteX(), y };
L('Rendering TextBracket: start:', start, 'stop:', stop, 'y:', y);
const bracket_height = this.render_options.bracket_height * this.position;
ctx.save();
this.applyStyle(ctx);
// Draw text
ctx.fillText(this.text, start.x, start.y);
// Get the width and height for the octave number
const main_measure = ctx.measureText(this.text);
const main_width = main_measure.width;
const main_height = main_measure.height;
// Calculate the y position for the super script
const super_y = start.y - main_height / 2.5;
// We called this.resetFont() in the constructor, so we know this.textFont is available.
// eslint-disable-next-line
const { family, size, weight, style } = this.textFont!;
// To draw the superscript, we scale the font size by 1/1.4.
const smallerFontSize = Font.scaleSize(size, 0.714286);
ctx.setFont(family, smallerFontSize, weight, style);
ctx.fillText(this.superscript, start.x + main_width + 1, super_y);
// Determine width and height of the superscript
const super_measure = ctx.measureText(this.superscript);
const super_width = super_measure.width;
const super_height = super_measure.height;
// Setup initial coordinates for the bracket line
let start_x = start.x;
let line_y = super_y;
const end_x = stop.x + this.stop.getGlyphProps().getWidth();
// Adjust x and y coordinates based on position
if (this.position === TextBracketPosition.TOP) {
start_x += main_width + super_width + 5;
line_y -= super_height / 2.7;
} else if (this.position === TextBracketPosition.BOTTOM) {
line_y += super_height / 2.7;
start_x += main_width + 2;
if (!this.render_options.underline_superscript) {
start_x += super_width;
}
}
if (this.render_options.dashed) {
// Main line
Renderer.drawDashedLine(ctx, start_x, line_y, end_x, line_y, this.render_options.dash);
// Ending Bracket
if (this.render_options.show_bracket) {
Renderer.drawDashedLine(
ctx,
end_x,
line_y + 1 * this.position,
end_x,
line_y + bracket_height,
this.render_options.dash
);
}
} else {
ctx.beginPath();
ctx.moveTo(start_x, line_y);
// Main line
ctx.lineTo(end_x, line_y);
if (this.render_options.show_bracket) {
// Ending bracket
ctx.lineTo(end_x, line_y + bracket_height);
}
ctx.stroke();
ctx.closePath();
}
ctx.restore();
}
}