Skip to content

Commit

Permalink
Use text metrics to calculate annotation widths
Browse files Browse the repository at this point in the history
  • Loading branch information
AaronDavidNewman committed Mar 10, 2021
1 parent dc97b0c commit 71e89f6
Show file tree
Hide file tree
Showing 2 changed files with 257 additions and 2 deletions.
13 changes: 12 additions & 1 deletion src/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { Vex } from './vex';
import { Flow } from './tables';
import { Modifier } from './modifier';
import { TextFont } from './textfont';

// To enable logging for this class. Set `Vex.Flow.Annotation.DEBUG` to `true`.
function L(...args) { if (Annotation.DEBUG) Vex.L('Vex.Flow.Annotation', args); }
Expand Down Expand Up @@ -62,8 +63,15 @@ export class Annotation extends Modifier {

let width = 0;
for (let i = 0; i < annotations.length; ++i) {
let testWidth = 0;
const annotation = annotations[i];
width = Math.max(annotation.getWidth(), width);
const textFont = TextFont.getTextFontFromVexFontData({ family: annotation.font.family,
size: annotation.font.size, weight: 'normal' });
textFont.setFontSize(annotation.font.size);
for (let j = 0; j < annotation.text.length; ++j) {
testWidth += textFont.getWidthForCharacter(annotation.text[j]) * (72 / 96);
}
width = Math.max(width, testWidth);
if (annotation.getPosition() === Modifier.Position.ABOVE) {
annotation.setTextLine(state.top_text_line);
state.top_text_line++;
Expand Down Expand Up @@ -145,6 +153,8 @@ export class Annotation extends Modifier {

// We're changing context parameters. Save current state.
this.context.save();
const classString = Object.keys(this.getAttribute('classes')).join(' ');
this.context.openGroup(classString, this.getAttribute('id'));
this.context.setFont(this.font.family, this.font.size, this.font.weight);
const text_width = this.context.measureText(this.text).width;

Expand Down Expand Up @@ -203,6 +213,7 @@ export class Annotation extends Modifier {

L('Rendering annotation: ', this.text, x, y);
this.context.fillText(this.text, x, y);
this.context.closeGroup();
this.context.restore();
}
}
246 changes: 245 additions & 1 deletion tests/annotation_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ VF.Test.Annotation = (function() {
var Annotation = {
Start: function() {
QUnit.module('Annotation');
runTests('Lyrics', Annotation.lyrics);
runTests('Simple Annotation', Annotation.simple);
runTests('Standard Notation Annotation', Annotation.standard);
runTests('Harmonics', Annotation.harmonic);
Expand All @@ -18,7 +19,250 @@ VF.Test.Annotation = (function() {
runTests('Test Justification Annotation Stem Down', Annotation.justificationStemDown);
runTests('TabNote Annotations', Annotation.tabNotes);
},

buildNotesFromJson: function(json) {
const rv = {
notes: [],
beamGroups: []
};
let currentBeam = [];
const beamNotes = () => {
if (currentBeam.length > 1) {
rv.beamGroups.push(new VF.Beam(currentBeam));
}
currentBeam = [];
};
json.notes.forEach((nn) => {
const keys = nn.pitches.map((pp) => pp.pitch.key);
const params = {
duration: nn.duration,
keys,
clef: nn.clef
};
const note = new VF.StaveNote(params);
nn.lyrics.forEach((lyric) => {
const vexL = new VF.Annotation(lyric.text);
vexL.setVerticalJustification(VF.Annotation.VerticalJustify.BOTTOM);
note.addAnnotation(lyric.verse, vexL);
});
if (nn.duration.indexOf('d') >= 0) {
note.addDotToAll();
}
if (note.ticks.value() <= 2048) {
currentBeam.push(note);
if (nn.endBeam) {
beamNotes();
}
} else {
beamNotes();
}
nn.pitches.forEach((pp, ix) => {
if (pp.pitch.accidental) {
note.addAccidental(ix, new VF.Accidental(pp.pitch.accidental));
}
});
rv.notes.push(note);
});
beamNotes();
rv.voice = new VF.Voice({
num_beats: json.beats.num_beats,
beat_value: json.beats.beat_value
});
rv.voice.addTickables(rv.notes);
return rv;
},
drawMusic(context, formatter, musicArray) {
let y = 40;
const voices = [];
musicArray.forEach((music) => {
formatter.joinVoices([music.voice]);
voices.push(music.voice);
});
const width = formatter.preCalculateMinTotalWidth(voices);
formatter.format(voices, width);
voices.forEach((voice) => {
const stave = new VF.Stave(10, y, width + 20);
y += 120;
stave.setContext(context).draw();
voice.draw(context, stave);
});
musicArray.forEach((music) => {
music.beamGroups.forEach((beam) => {
beam.setContext(context).draw();
});
});
},
lyrics: function(options) {
const json1 =
[
{
'notes': [
{
'pitches': [
{
'pitch': {
'key': 'cn/4'
}
},
{
'pitch': {
'key': 'fn/4'
}
}
],
'duration': '2',
'lyrics': [
{
'text': 'hand,',
'verse': 0
},
{
'text': 'pears ',
'verse': 1
}
],
'clef': 'treble',
'endBeam': false
},
{
'pitches': [
{
'pitch': {
'key': 'cn/4'
}
},
{
'pitch': {
'key': 'an/4'
}
}
],
'duration': '8',
'lyrics': [
{
'text': 'and  ',
'verse': 1
},
{
'text': 'lead  ',
'verse': 0
}
],
'clef': 'treble',
'endBeam': false
},
{
'pitches': [
{
'pitch': {
'key': 'c#/4',
'accidental': '#'
}
},
{
'pitch': {
'key': 'an/4'
}
}
],
'duration': '8',
'lyrics': [
{
'text': 'me',
'verse': 0
},
{
'text': 'the',
'verse': 1
}
],
'clef': 'treble',
'endBeam': false
}
],
'beats': {
'num_beats': 3,
'beat_value': 4
}
}
];
const json2 = [
{
'notes': [
{
'pitches': [
{
'pitch': {
'key': 'an/2'
}
},
{
'pitch': {
'key': 'fn/3'
}
}
],
'duration': '2',
'lyrics': [
{
'text': ' ',
'verse': 0
}
],
'clef': 'bass',
'endBeam': false
},
{
'pitches': [
{
'pitch': {
'key': 'fn/2'
}
},
{
'pitch': {
'key': 'fn/3'
}
}
],
'duration': '8',
'lyrics': [],
'clef': 'bass',
'endBeam': false
},
{
'pitches': [
{
'pitch': {
'key': 'fn/2'
}
},
{
'pitch': {
'key': 'fn/3'
}
}
],
'duration': '8',
'lyrics': [],
'clef': 'bass',
'endBeam': false
}
],
'beats': {
'num_beats': 3,
'beat_value': 4
}
}
];
var vf = VF.Test.makeFactory(options, 750, 280);
const context = vf.getContext();
const formatter = new VF.Formatter({ softmaxFactor: 100 });
const musicArray = [];
musicArray.push(Annotation.buildNotesFromJson(json1[0]));
musicArray.push(Annotation.buildNotesFromJson(json2[0]));
Annotation.drawMusic(context, formatter, musicArray);
ok(true);
},
simple: function(options, contextBuilder) {
var ctx = contextBuilder(options.elementId, 500, 240);
ctx.scale(1.5, 1.5); ctx.fillStyle = '#221'; ctx.strokeStyle = '#221';
Expand Down

0 comments on commit 71e89f6

Please sign in to comment.