UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature.

199 lines (170 loc) 5.77 kB
// VexFlow - Music Engraving for HTML5 // Copyright Mohit Muthanna 2010 // Author Larry Kuhns 2013 // Class to draws string numbers into the notation. import { Builder } from './easyscore'; import { Font, FontInfo, FontStyle, FontWeight } from './font'; import { Modifier, ModifierPosition } from './modifier'; import { ModifierContextState } from './modifiercontext'; import { StemmableNote } from './stemmablenote'; import { Tables } from './tables'; import { TextFormatter } from './textformatter'; import { Category } from './typeguard'; import { RuntimeError } from './util'; export class FretHandFinger extends Modifier { static get CATEGORY(): string { return Category.FretHandFinger; } static TEXT_FONT: Required<FontInfo> = { family: Font.SANS_SERIF, size: 9, weight: FontWeight.BOLD, style: FontStyle.NORMAL, }; // Arrange fingerings inside a ModifierContext. static format(nums: FretHandFinger[], state: ModifierContextState): boolean { const { left_shift, right_shift } = state; const num_spacing = 1; if (!nums || nums.length === 0) return false; const nums_list = []; let prev_note = null; let shiftLeft = 0; let shiftRight = 0; for (let i = 0; i < nums.length; ++i) { const num = nums[i]; const note = num.getNote(); const pos = num.getPosition(); const index = num.checkIndex(); const props = note.getKeyProps()[index]; const textFormatter = TextFormatter.create(num.textFont); const textHeight = textFormatter.maxHeight; if (num.position === ModifierPosition.ABOVE) { state.top_text_line += textHeight / Tables.STAVE_LINE_DISTANCE + 0.5; } if (num.position === ModifierPosition.BELOW) { state.text_line += textHeight / Tables.STAVE_LINE_DISTANCE + 0.5; } if (note !== prev_note) { for (let n = 0; n < note.keys.length; ++n) { if (left_shift === 0) { shiftLeft = Math.max(note.getLeftDisplacedHeadPx(), shiftLeft); } if (right_shift === 0) { shiftRight = Math.max(note.getRightDisplacedHeadPx(), shiftRight); } } prev_note = note; } nums_list.push({ note, num, pos, line: props.line, shiftL: shiftLeft, shiftR: shiftRight, }); } // Sort fingernumbers by line number. nums_list.sort((a, b) => b.line - a.line); let numShiftL = 0; let numShiftR = 0; let xWidthL = 0; let xWidthR = 0; let lastLine = null; let lastNote = null; for (let i = 0; i < nums_list.length; ++i) { let num_shift = 0; const { note, pos, num, line, shiftL, shiftR } = nums_list[i]; // Reset the position of the string number every line. if (line !== lastLine || note !== lastNote) { numShiftL = left_shift + shiftL; numShiftR = right_shift + shiftR; } const numWidth = num.getWidth() + num_spacing; if (pos === Modifier.Position.LEFT) { num.setXShift(left_shift + numShiftL); num_shift = left_shift + numWidth; // spacing xWidthL = num_shift > xWidthL ? num_shift : xWidthL; } else if (pos === Modifier.Position.RIGHT) { num.setXShift(numShiftR); num_shift = shiftRight + numWidth; // spacing xWidthR = num_shift > xWidthR ? num_shift : xWidthR; } lastLine = line; lastNote = note; } state.left_shift += xWidthL; state.right_shift += xWidthR; return true; } static easyScoreHook({ fingerings }: { fingerings?: string } = {}, note: StemmableNote, builder: Builder): void { fingerings ?.split(',') .map((fingeringString: string) => { const split = fingeringString.trim().split('.'); const params: { number: string; position?: string } = { number: split[0] }; if (split[1]) params.position = split[1]; return builder.getFactory().Fingering(params); }) .map((fingering: Modifier, index: number) => note.addModifier(fingering, index)); } protected finger: string; protected x_offset: number; protected y_offset: number; constructor(finger: string) { super(); this.finger = finger; this.width = 7; this.position = Modifier.Position.LEFT; // Default position above stem or note head this.x_shift = 0; this.y_shift = 0; this.x_offset = 0; // Horizontal offset from default this.y_offset = 0; // Vertical offset from default this.resetFont(); } setFretHandFinger(finger: string): this { this.finger = finger; return this; } getFretHandFinger(): string { return this.finger; } setOffsetX(x: number): this { this.x_offset = x; return this; } setOffsetY(y: number): this { this.y_offset = y; return this; } draw(): void { const ctx = this.checkContext(); const note = this.checkAttachedNote(); this.setRendered(); const start = note.getModifierStartXY(this.position, this.index); let dot_x = start.x + this.x_shift + this.x_offset; let dot_y = start.y + this.y_shift + this.y_offset + 5; switch (this.position) { case Modifier.Position.ABOVE: dot_x -= 4; dot_y -= 12; break; case Modifier.Position.BELOW: dot_x -= 2; dot_y += 10; break; case Modifier.Position.LEFT: dot_x -= this.width; break; case Modifier.Position.RIGHT: dot_x += 1; break; default: throw new RuntimeError('InvalidPosition', `The position ${this.position} does not exist`); } ctx.save(); ctx.setFont(this.textFont); ctx.fillText('' + this.finger, dot_x, dot_y); ctx.restore(); } }