UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature.

240 lines (239 loc) 10.9 kB
import { Element } from './element.js'; import { Modifier, ModifierPosition } from './modifier.js'; import { Stem } from './stem.js'; import { Tables } from './tables.js'; import { TextFormatter } from './textformatter.js'; import { isStemmableNote, isTabNote } from './typeguard.js'; import { log } from './util.js'; function L(...args) { if (Annotation.DEBUG) log('Vex.Flow.Annotation', args); } export var AnnotationHorizontalJustify; (function (AnnotationHorizontalJustify) { AnnotationHorizontalJustify[AnnotationHorizontalJustify["LEFT"] = 1] = "LEFT"; AnnotationHorizontalJustify[AnnotationHorizontalJustify["CENTER"] = 2] = "CENTER"; AnnotationHorizontalJustify[AnnotationHorizontalJustify["RIGHT"] = 3] = "RIGHT"; AnnotationHorizontalJustify[AnnotationHorizontalJustify["CENTER_STEM"] = 4] = "CENTER_STEM"; })(AnnotationHorizontalJustify || (AnnotationHorizontalJustify = {})); export var AnnotationVerticalJustify; (function (AnnotationVerticalJustify) { AnnotationVerticalJustify[AnnotationVerticalJustify["TOP"] = 1] = "TOP"; AnnotationVerticalJustify[AnnotationVerticalJustify["CENTER"] = 2] = "CENTER"; AnnotationVerticalJustify[AnnotationVerticalJustify["BOTTOM"] = 3] = "BOTTOM"; AnnotationVerticalJustify[AnnotationVerticalJustify["CENTER_STEM"] = 4] = "CENTER_STEM"; })(AnnotationVerticalJustify || (AnnotationVerticalJustify = {})); class Annotation extends Modifier { static get CATEGORY() { return "Annotation"; } static get minAnnotationPadding() { const musicFont = Tables.currentMusicFont(); return musicFont.lookupMetric('noteHead.minPadding'); } static format(annotations, state) { if (!annotations || annotations.length === 0) return false; let leftWidth = 0; let rightWidth = 0; let maxLeftGlyphWidth = 0; let maxRightGlyphWidth = 0; for (let i = 0; i < annotations.length; ++i) { const annotation = annotations[i]; const textFormatter = TextFormatter.create(annotation.textFont); const textLines = (2 + textFormatter.getYForStringInPx(annotation.text).height) / Tables.STAVE_LINE_DISTANCE; let verticalSpaceNeeded = textLines; const note = annotation.checkAttachedNote(); const glyphWidth = note.getGlyphProps().getWidth(); const textWidth = textFormatter.getWidthForTextInPx(annotation.text); if (annotation.horizontalJustification === AnnotationHorizontalJustify.LEFT) { maxLeftGlyphWidth = Math.max(glyphWidth, maxLeftGlyphWidth); leftWidth = Math.max(leftWidth, textWidth) + Annotation.minAnnotationPadding; } else if (annotation.horizontalJustification === AnnotationHorizontalJustify.RIGHT) { maxRightGlyphWidth = Math.max(glyphWidth, maxRightGlyphWidth); rightWidth = Math.max(rightWidth, textWidth); } else { leftWidth = Math.max(leftWidth, textWidth / 2) + Annotation.minAnnotationPadding; rightWidth = Math.max(rightWidth, textWidth / 2); maxLeftGlyphWidth = Math.max(glyphWidth / 2, maxLeftGlyphWidth); maxRightGlyphWidth = Math.max(glyphWidth / 2, maxRightGlyphWidth); } const stave = note.getStave(); const stemDirection = note.hasStem() ? note.getStemDirection() : Stem.UP; let stemHeight = 0; let lines = 5; if (isTabNote(note)) { if (note.render_options.draw_stem) { const stem = note.getStem(); if (stem) { stemHeight = Math.abs(stem.getHeight()) / Tables.STAVE_LINE_DISTANCE; } } else { stemHeight = 0; } } else if (isStemmableNote(note)) { const stem = note.getStem(); if (stem && note.getNoteType() === 'n') { stemHeight = Math.abs(stem.getHeight()) / Tables.STAVE_LINE_DISTANCE; } } if (stave) { lines = stave.getNumLines(); } if (annotation.verticalJustification === this.VerticalJustify.TOP) { let noteLine = note.getLineNumber(true); if (isTabNote(note)) { noteLine = lines - (note.leastString() - 0.5); } if (stemDirection === Stem.UP) { noteLine += stemHeight; } const curTop = noteLine + state.top_text_line + 0.5; if (curTop < lines) { annotation.setTextLine(lines - noteLine); verticalSpaceNeeded += lines - noteLine; state.top_text_line = verticalSpaceNeeded; } else { annotation.setTextLine(state.top_text_line); state.top_text_line += verticalSpaceNeeded; } } else if (annotation.verticalJustification === this.VerticalJustify.BOTTOM) { let noteLine = lines - note.getLineNumber(); if (isTabNote(note)) { noteLine = note.greatestString() - 1; } if (stemDirection === Stem.DOWN) { noteLine += stemHeight; } const curBottom = noteLine + state.text_line + 1; if (curBottom < lines) { annotation.setTextLine(lines - curBottom); verticalSpaceNeeded += lines - curBottom; state.text_line = verticalSpaceNeeded; } else { annotation.setTextLine(state.text_line); state.text_line += verticalSpaceNeeded; } } else { annotation.setTextLine(state.text_line); } } const rightOverlap = Math.min(Math.max(rightWidth - maxRightGlyphWidth, 0), Math.max(rightWidth - state.right_shift, 0)); const leftOverlap = Math.min(Math.max(leftWidth - maxLeftGlyphWidth, 0), Math.max(leftWidth - state.left_shift, 0)); state.left_shift += leftOverlap; state.right_shift += rightOverlap; return true; } constructor(text) { super(); this.text = text; this.horizontalJustification = AnnotationHorizontalJustify.CENTER; this.verticalJustification = AnnotationVerticalJustify.TOP; this.resetFont(); this.setWidth(Tables.textWidth(text)); } setVerticalJustification(just) { this.verticalJustification = typeof just === 'string' ? Annotation.VerticalJustifyString[just] : just; return this; } getJustification() { return this.horizontalJustification; } setJustification(just) { this.horizontalJustification = typeof just === 'string' ? Annotation.HorizontalJustifyString[just] : just; return this; } draw() { const ctx = this.checkContext(); const note = this.checkAttachedNote(); const stemDirection = note.hasStem() ? note.getStemDirection() : Stem.UP; const textFormatter = TextFormatter.create(this.textFont); const start = note.getModifierStartXY(ModifierPosition.ABOVE, this.index); this.setRendered(); ctx.save(); this.applyStyle(); ctx.openGroup('annotation', this.getAttribute('id')); ctx.setFont(this.textFont); const text_width = textFormatter.getWidthForTextInPx(this.text); const text_height = textFormatter.getYForStringInPx(this.text).height; let x; let y; if (this.horizontalJustification === AnnotationHorizontalJustify.LEFT) { x = start.x; } else if (this.horizontalJustification === AnnotationHorizontalJustify.RIGHT) { x = start.x - text_width; } else if (this.horizontalJustification === AnnotationHorizontalJustify.CENTER) { x = start.x - text_width / 2; } else { x = note.getStemX() - text_width / 2; } let stem_ext = {}; let spacing = 0; const has_stem = note.hasStem(); const stave = note.checkStave(); if (has_stem) { stem_ext = note.checkStem().getExtents(); spacing = stave.getSpacingBetweenLines(); } if (this.verticalJustification === AnnotationVerticalJustify.BOTTOM) { const ys = note.getYs(); y = ys.reduce((a, b) => (a > b ? a : b)); y += (this.text_line + 1) * Tables.STAVE_LINE_DISTANCE + text_height; if (has_stem && stemDirection === Stem.DOWN) { y = Math.max(y, stem_ext.topY + text_height + spacing * this.text_line); } } else if (this.verticalJustification === AnnotationVerticalJustify.CENTER) { const yt = note.getYForTopText(this.text_line) - 1; const yb = stave.getYForBottomText(this.text_line); y = yt + (yb - yt) / 2 + text_height / 2; } else if (this.verticalJustification === AnnotationVerticalJustify.TOP) { const topY = Math.min(...note.getYs()); y = topY - (this.text_line + 1) * Tables.STAVE_LINE_DISTANCE; if (has_stem && stemDirection === Stem.UP) { spacing = stem_ext.topY < stave.getTopLineTopY() ? Tables.STAVE_LINE_DISTANCE : spacing; y = Math.min(y, stem_ext.topY - spacing * (this.text_line + 1)); } } else { const extents = note.getStemExtents(); y = extents.topY + (extents.baseY - extents.topY) / 2 + text_height / 2; } L('Rendering annotation: ', this.text, x, y); ctx.fillText(this.text, x, y); ctx.closeGroup(); this.restoreStyle(); ctx.restore(); } } Annotation.DEBUG = false; Annotation.TEXT_FONT = Object.assign({}, Element.TEXT_FONT); Annotation.HorizontalJustify = AnnotationHorizontalJustify; Annotation.HorizontalJustifyString = { left: AnnotationHorizontalJustify.LEFT, right: AnnotationHorizontalJustify.RIGHT, center: AnnotationHorizontalJustify.CENTER, centerStem: AnnotationHorizontalJustify.CENTER_STEM, }; Annotation.VerticalJustify = AnnotationVerticalJustify; Annotation.VerticalJustifyString = { above: AnnotationVerticalJustify.TOP, top: AnnotationVerticalJustify.TOP, below: AnnotationVerticalJustify.BOTTOM, bottom: AnnotationVerticalJustify.BOTTOM, center: AnnotationVerticalJustify.CENTER, centerStem: AnnotationVerticalJustify.CENTER_STEM, }; export { Annotation };