VexFlow - Music Engraving for HTML5 Copyright Mohit Muthanna 2010 Author Larry Kuhns 2013 Class to draws string numbers into the notation.

import { Vex } from './vex';
import { Modifier } from './modifier';

/**
 * @constructor
 */
export class FretHandFinger extends Modifier {
  static get CATEGORY() { return 'frethandfinger'; }

Arrange fingerings inside a ModifierContext.

  static format(nums, state) {
    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 props = note.getKeyProps()[num.getIndex()];
      if (note !== prev_note) {
        for (let n = 0; n < note.keys.length; ++n) {
          const props_tmp = note.getKeyProps()[n];
          if (left_shift === 0) {
            shiftLeft = props_tmp.displaced ? note.getExtraLeftPx() : shiftLeft;
          }
          if (right_shift === 0) {
            shiftRight = props_tmp.displaced ? note.getExtraRightPx() : 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;
  }

  constructor(number) {
    super();
    this.attrs.type = 'FretHandFinger';

    this.note = null;
    this.index = null;
    this.finger = number;
    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.font = {
      family: 'sans-serif',
      size: 9,
      weight: 'bold',
    };
  }
  getCategory() { return FretHandFinger.CATEGORY; }
  getNote() { return this.note; }
  setNote(note) { this.note = note; return this; }
  getIndex() { return this.index; }
  setIndex(index) { this.index = index; return this; }
  getPosition() { return this.position; }
  setPosition(position) {
    if (position >= Modifier.Position.LEFT && position <= Modifier.Position.BELOW) {
      this.position = position;
    }
    return this;
  }
  setFretHandFinger(number) { this.finger = number; return this; }
  setOffsetX(x) { this.x_offset = x; return this; }
  setOffsetY(y) { this.y_offset = y; return this; }

  draw() {
    this.checkContext();

    if (!this.note || this.index == null) {
      throw new Vex.RERR('NoAttachedNote', "Can't draw string number without a note and index.");
    }

    const ctx = this.context;
    const start = this.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 Vex.RERR('InvalidPostion', `The position ${this.position} does not exist`);
    }

    ctx.save();
    ctx.setFont(this.font.family, this.font.size, this.font.weight);
    ctx.fillText('' + this.finger, dot_x, dot_y);
    ctx.restore();
  }
}
h