UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature.

225 lines (186 loc) 6.74 kB
// [VexFlow](https://vexflow.com) - Copyright (c) Mohit Muthanna 2010. // // ## Description // This file implements the `Stem` object. Generally this object is handled // by its parent `StemmableNote`. import { BoundingBox } from './boundingbox'; import { Element } from './element'; import { Tables } from './tables'; import { Category } from './typeguard'; import { log, RuntimeError } from './util'; // eslint-disable-next-line function L(...args: any[]) { if (Stem.DEBUG) log('Vex.Flow.Stem', args); } export interface StemOptions { stem_down_y_base_offset?: number; stem_up_y_base_offset?: number; stem_down_y_offset?: number; stem_up_y_offset?: number; stemletHeight?: number; isStemlet?: boolean; hide?: boolean; stem_direction?: number; stem_extension?: number; y_bottom?: number; y_top?: number; x_end?: number; x_begin?: number; } export class Stem extends Element { /** To enable logging for this class. Set `Vex.Flow.Stem.DEBUG` to `true`. */ static DEBUG: boolean = false; static get CATEGORY(): string { return Category.Stem; } // Stem directions static get UP(): number { return 1; } static get DOWN(): number { return -1; } // Theme static get WIDTH(): number { return Tables.STEM_WIDTH; } static get HEIGHT(): number { return Tables.STEM_HEIGHT; } protected hide: boolean; protected isStemlet: boolean; protected stemletHeight: number; protected x_begin: number; protected x_end: number; protected y_top: number; protected stem_up_y_offset: number = 0; protected y_bottom: number; protected stem_down_y_offset: number = 0; protected stem_up_y_base_offset: number = 0; protected stem_down_y_base_offset: number = 0; protected stem_direction: number; protected stem_extension: number; protected renderHeightAdjustment: number; constructor(options?: StemOptions) { super(); // Default notehead x bounds this.x_begin = options?.x_begin || 0; this.x_end = options?.x_end || 0; // Y bounds for top/bottom most notehead this.y_top = options?.y_top || 0; this.y_bottom = options?.y_bottom || 0; // Stem top extension this.stem_extension = options?.stem_extension || 0; // Direction of the stem this.stem_direction = options?.stem_direction || 0; // Flag to override all draw calls this.hide = options?.hide || false; this.isStemlet = options?.isStemlet || false; this.stemletHeight = options?.stemletHeight || 0; // Use to adjust the rendered height without affecting // the results of `.getExtents()` this.renderHeightAdjustment = 0; this.setOptions(options); } setOptions(options?: StemOptions): void { // Changing where the stem meets the head this.stem_up_y_offset = options?.stem_up_y_offset || 0; this.stem_down_y_offset = options?.stem_down_y_offset || 0; this.stem_up_y_base_offset = options?.stem_up_y_base_offset || 0; this.stem_down_y_base_offset = options?.stem_down_y_base_offset || 0; } // Set the x bounds for the default notehead setNoteHeadXBounds(x_begin: number, x_end: number): this { this.x_begin = x_begin; this.x_end = x_end; return this; } // Set the direction of the stem in relation to the noteheads setDirection(direction: number): void { this.stem_direction = direction; } // Set the extension for the stem, generally for flags or beams setExtension(ext: number): void { this.stem_extension = ext; } getExtension(): number { return this.stem_extension; } // The the y bounds for the top and bottom noteheads setYBounds(y_top: number, y_bottom: number): void { this.y_top = y_top; this.y_bottom = y_bottom; } // Gets the entire height for the stem getHeight(): number { const y_offset = this.stem_direction === Stem.UP ? this.stem_up_y_offset : this.stem_down_y_offset; const unsigned_height = this.y_bottom - this.y_top + (Stem.HEIGHT - y_offset + this.stem_extension); // parentheses just for grouping. return unsigned_height * this.stem_direction; } getBoundingBox(): BoundingBox { throw new RuntimeError('NotImplemented', 'getBoundingBox() not implemented.'); } // Get the y coordinates for the very base of the stem to the top of // the extension getExtents(): { topY: number; baseY: number } { const isStemUp = this.stem_direction === Stem.UP; const ys = [this.y_top, this.y_bottom]; const stemHeight = Stem.HEIGHT + this.stem_extension; const innerMostNoteheadY = (isStemUp ? Math.min : Math.max)(...ys); const outerMostNoteheadY = (isStemUp ? Math.max : Math.min)(...ys); const stemTipY = innerMostNoteheadY + stemHeight * -this.stem_direction; return { topY: stemTipY, baseY: outerMostNoteheadY }; } setVisibility(isVisible: boolean): this { this.hide = !isVisible; return this; } setStemlet(isStemlet: boolean, stemletHeight: number): this { this.isStemlet = isStemlet; this.stemletHeight = stemletHeight; return this; } adjustHeightForFlag(): void { this.renderHeightAdjustment = Tables.currentMusicFont().lookupMetric('stem.heightAdjustmentForFlag', -3); } adjustHeightForBeam(): void { this.renderHeightAdjustment = -Stem.WIDTH / 2; } // Render the stem onto the canvas draw(): void { this.setRendered(); if (this.hide) return; const ctx = this.checkContext(); let stem_x; let stem_y; const stem_direction = this.stem_direction; let y_base_offset: number = 0; if (stem_direction === Stem.DOWN) { // Down stems are rendered to the left of the head. stem_x = this.x_begin; stem_y = this.y_top + this.stem_down_y_offset; y_base_offset = this.stem_down_y_base_offset; } else { // Up stems are rendered to the right of the head. stem_x = this.x_end; stem_y = this.y_bottom - this.stem_up_y_offset; y_base_offset = this.stem_up_y_base_offset; } const stemHeight = this.getHeight(); L('Rendering stem - ', 'Top Y: ', this.y_top, 'Bottom Y: ', this.y_bottom); // The offset from the stem's base which is required fo satisfy the stemlet height const stemletYOffset = this.isStemlet ? stemHeight - this.stemletHeight * this.stem_direction : 0; // Draw the stem ctx.save(); this.applyStyle(); ctx.openGroup('stem', this.getAttribute('id'), { pointerBBox: true }); ctx.beginPath(); ctx.setLineWidth(Stem.WIDTH); ctx.moveTo(stem_x, stem_y - stemletYOffset + y_base_offset); ctx.lineTo(stem_x, stem_y - stemHeight - this.renderHeightAdjustment * stem_direction); ctx.stroke(); ctx.closeGroup(); this.restoreStyle(); ctx.restore(); } }