Skip to content

Commit

Permalink
Merge branch 'master' into test
Browse files Browse the repository at this point in the history
  • Loading branch information
ronyeh committed Jan 16, 2022
2 parents 2574276 + 54966e3 commit c3365ff
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 66 deletions.
126 changes: 87 additions & 39 deletions src/annotation.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// [VexFlow](https://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
// MIT License

import { Element } from './element';
import { log } from './util';
import { FontInfo } from './font';
import { Modifier, ModifierPosition } from './modifier';
import { ModifierContextState } from './modifiercontext';
import { ModifierContextState } from './modifiercontext';
import { StemmableNote } from './stemmablenote';
import { Tables } from './tables';
import { TextFormatter } from './textformatter';
import { log } from './util';
import { Modifier, ModifierPosition } from './modifier';
import { Stave } from './stave';
import { Stem } from './stem';
import { Tables } from './tables';
import { TabNote } from './tabnote';

// eslint-disable-next-line
function L(...args: any[]) {
Expand Down Expand Up @@ -70,32 +72,77 @@ export class Annotation extends Modifier {
/** Arrange annotations within a `ModifierContext` */
static format(annotations: Annotation[], state: ModifierContextState): boolean {
if (!annotations || annotations.length === 0) return false;

let width = 0;
for (let i = 0; i < annotations.length; ++i) {
const annotation = annotations[i];
const textFormatter = TextFormatter.create(annotation.textFont);

// Text height is expressed in fractional stave spaces.
const textLines = (5 + textFormatter.maxHeight) / Tables.STAVE_LINE_DISTANCE;
let verticalSpaceNeeded = textLines;

const note = annotation.checkAttachedNote();
const stave: Stave | undefined = note.getStave();
const stemDirection = note.hasStem() ? note.getStemDirection() : Stem.UP;
let stemHeight = 0;
let lines = 5;
if (note instanceof TabNote) {
if (note.render_options.draw_stem) {
const stem = (note as StemmableNote).getStem();
if (stem) {
stemHeight = Math.abs(stem.getHeight()) / Tables.STAVE_LINE_DISTANCE;
}
} else {
stemHeight = 0;
}
} else if (note instanceof StemmableNote) {
const stem = (note as StemmableNote).getStem();
if (stem && note.getNoteType() === 'n') {
stemHeight = Math.abs(stem.getHeight()) / Tables.STAVE_LINE_DISTANCE;
}
}
if (stave) {
lines = stave.getNumLines();
}
// Get the text width from the font metrics.
const textWidth = textFormatter.getWidthForTextInPx(annotation.text);
width = Math.max(width, textWidth);

if (annotation.getPosition() === ModifierPosition.ABOVE) {
annotation.setTextLine(state.top_text_line);
// Like in CSS, lineHeight is multiplied by the font size.
const lineHeight = 1.4;
// This is expressed in fractional stave spaces.
const verticalSpaceNeeded = (lineHeight * textFormatter.maxHeight) / Tables.STAVE_LINE_DISTANCE;
// Each subsequent annotation is shifted downward by 1.4 lines.
state.top_text_line += verticalSpaceNeeded;
if (annotation.verticalJustification === this.VerticalJustify.TOP) {
let noteLine = note.getLineNumber(true);
if (note instanceof TabNote) {
noteLine = lines - ((note as TabNote).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 (note instanceof TabNote) {
noteLine = (note as TabNote).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);
// Like in CSS, lineHeight is multiplied by the font size.
const lineHeight = 1.1;
// This is expressed in fractional stave spaces.
const verticalSpaceNeeded = (lineHeight * textFormatter.maxHeight) / Tables.STAVE_LINE_DISTANCE;
// Each subsequent annotation is shifted upward by 1.1 lines.
state.text_line += verticalSpaceNeeded;
}
}
state.left_shift += width / 2;
Expand All @@ -117,13 +164,14 @@ export class Annotation extends Modifier {

this.text = text;
this.horizontalJustification = AnnotationHorizontalJustify.CENTER;
// warning: the default in the constructor is TOP, but in the factory the default is BOTTOM.
// this is to support legacy application that may expect this.
this.verticalJustification = AnnotationVerticalJustify.TOP;
this.resetFont();

// The default width is calculated from the text.
this.setWidth(Tables.textWidth(text));
}

/**
* Set vertical position of text (above or below stave).
* @param just value in `AnnotationVerticalJustify`.
Expand Down Expand Up @@ -153,23 +201,20 @@ export class Annotation extends Modifier {
draw(): void {
const ctx = this.checkContext();
const note = this.checkAttachedNote();
this.setRendered();

const stemDirection = note.hasStem() ? note.getStemDirection() : Stem.UP;
const textFormatter = TextFormatter.create(this.textFont);
const start = note.getModifierStartXY(ModifierPosition.ABOVE, this.index);

this.setRendered();

// We're changing context parameters. Save current state.
ctx.save();
const classString = Object.keys(this.getAttribute('classes')).join(' ');
ctx.openGroup(classString, this.getAttribute('id'));
ctx.setFont(this.textFont);

const text_width = ctx.measureText(this.text).width;

// Estimate text height to be the same as the width of an 'm'.
//
// This is a hack to work around the inability to measure text height
// in HTML5 Canvas (and SVG).
const text_height = ctx.measureText('m').width;
const text_height = textFormatter.maxHeight + 2;
let x;
let y;

Expand All @@ -196,21 +241,24 @@ export class Annotation extends Modifier {
}

if (this.verticalJustification === AnnotationVerticalJustify.BOTTOM) {
// HACK: We need to compensate for the text's height since its origin
// is bottom-right.
y = stave.getYForBottomText(this.text_line + Tables.TEXT_HEIGHT_OFFSET_HACK);
if (has_stem) {
const stem_base = note.getStemDirection() === 1 ? stem_ext.baseY : stem_ext.topY;
y = Math.max(y, stem_base + spacing * (this.text_line + 2));
// Use the largest (lowest) Y value
const ys: number[] = 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) {
y = Math.min(stave.getYForTopText(this.text_line), note.getYs()[0] - 10);
if (has_stem) {
y = Math.min(y, stem_ext.topY - 5 - spacing * this.text_line);
y = note.getYs()[0] - (this.text_line + 1) * Tables.STAVE_LINE_DISTANCE;
if (has_stem && stemDirection === Stem.UP) {
// If the stem is above the stave already, go with default line width vs. actual
// since the lines between don't really matter.
spacing = stem_ext.topY < stave.getTopLineTopY() ? Tables.STAVE_LINE_DISTANCE : spacing;
y = Math.min(y, stem_ext.topY - spacing * (this.text_line + 1));
}
} /* CENTER_STEM */ else {
const extents = note.getStemExtents();
Expand Down
12 changes: 6 additions & 6 deletions src/articulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,13 +217,13 @@ export class Articulation extends Modifier {
articulations.forEach((articulation) => {
const note = articulation.checkAttachedNote();
let lines = 5;
const stemDirection = note.getStemDirection();
const stemDirection = note.hasStem() ? note.getStemDirection() : Stem.UP;
let stemHeight = 0;
// Decide if we need to consider beam direction in placement.
if (note instanceof StemmableNote) {
const stem = (note as StemmableNote).getStem();
if (stem && note.getStemDirection() == Stem.DOWN) {
stemHeight = stem.getHeight() / 5;
if (stem) {
stemHeight = Math.abs(stem.getHeight()) / Tables.STAVE_LINE_DISTANCE;
}
}
const stave: Stave | undefined = note.getStave();
Expand All @@ -236,20 +236,20 @@ export class Articulation extends Modifier {
noteLine += stemHeight;
}
let increment = getIncrement(articulation, state.top_text_line, ABOVE);
const curTop = noteLine + state.text_line + 0.5;
const curTop = noteLine + state.top_text_line + 0.5;
// If articulation must be above stave, add lines between note and stave top
if (!articulation.articulation.between_lines && curTop < lines) {
increment += lines - curTop;
}
articulation.setTextLine(state.top_text_line);
state.top_text_line += increment;
} else if (articulation.getPosition() === BELOW) {
let noteLine = note.getLineNumber();
let noteLine = Math.max(lines - note.getLineNumber(), 0);
if (stemDirection === Stem.DOWN) {
noteLine += stemHeight;
}
let increment = getIncrement(articulation, state.text_line, BELOW);
const curBottom = lines - noteLine + state.text_line + 1.5;
const curBottom = noteLine + state.text_line + 0.5;
// if articulation must be below stave, add lines from note to stave bottom
if (!articulation.articulation.between_lines && curBottom < lines) {
increment += lines - curBottom;
Expand Down
21 changes: 15 additions & 6 deletions src/bend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { Element } from './element';
import { FontInfo } from './font';
import { Modifier } from './modifier';
import { ModifierContextState } from './modifiercontext';
import { Stave } from './stave';
import { Tables } from './tables';
import { TabNote } from './tabnote';
import { RuntimeError } from './util';

export interface BendPhrase {
Expand Down Expand Up @@ -38,15 +40,19 @@ export class Bend extends Modifier {
if (!bends || bends.length === 0) return false;

let last_width = 0;
// Bends are always on top.
const text_line = state.top_text_line;

// Format Bends
for (let i = 0; i < bends.length; ++i) {
const bend = bends[i];
const note = bend.checkAttachedNote();
if (note instanceof TabNote) {
const stringPos = (note as TabNote).leastString() - 1;
if (state.top_text_line < stringPos) {
state.top_text_line = stringPos;
}
}
bend.setXShift(last_width);
last_width = bend.getWidth();
bend.setTextLine(text_line);
bend.setTextLine(state.top_text_line);
}

state.right_shift += last_width;
Expand Down Expand Up @@ -179,8 +185,11 @@ export class Bend extends Modifier {
const x_shift = this.x_shift;

const stave = note.checkStave();
const bend_height = stave.getYForTopText(this.text_line) + 3;
const annotation_y = stave.getYForTopText(this.text_line) - 1;
const spacing = stave.getSpacingBetweenLines();
const lowestY = note.getYs().reduce((a, b) => a < b ? a : b);
// this.text_line is relative to top string in the group.
const bend_height = start.y - ((this.text_line + 1) * spacing + start.y - lowestY) + 3;
const annotation_y = start.y - ((this.text_line + 1) * spacing + start.y - lowestY) - 1;

const renderBend = (x: number, y: number, width: number, height: number) => {
const cp_x = x + width;
Expand Down
2 changes: 1 addition & 1 deletion src/canvascontext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class CanvasContext extends RenderContext {
}

// eslint-disable-next-line
openGroup(cls: string, id?: string, attrs?: GroupAttributes): any {
openGroup(cls?: string, id?: string, attrs?: GroupAttributes): any {
// Containers not implemented.
}

Expand Down
2 changes: 1 addition & 1 deletion src/rendercontext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export abstract class RenderContext {
abstract save(): this;
abstract restore(): this;
// eslint-disable-next-line
abstract openGroup(cls: string, id?: string, attrs?: GroupAttributes): any;
abstract openGroup(cls?: string, id?: string, attrs?: GroupAttributes): any;
abstract closeGroup(): void;
// eslint-disable-next-line
abstract add(child: any): void;
Expand Down
4 changes: 2 additions & 2 deletions src/svgcontext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export class SVGContext extends RenderContext {
}

// Allow grouping elements in containers for interactivity.
openGroup(cls: string, id?: string, attrs?: GroupAttributes): SVGGElement {
openGroup(cls?: string, id?: string, attrs?: GroupAttributes): SVGGElement {
const group = this.create('g');
this.groups.push(group);
this.parent.appendChild(group);
Expand Down Expand Up @@ -496,7 +496,7 @@ export class SVGContext extends RenderContext {
return `filter: drop-shadow(0 0 ${sa.width / 1.5}px ${sa.color})`;
}

fill(attributes: Attributes): this {
fill(attributes?: Attributes): this {
const path = this.create('path');
if (typeof attributes === 'undefined') {
attributes = { ...this.attributes, stroke: 'none' };
Expand Down
8 changes: 8 additions & 0 deletions src/tabnote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,14 @@ export class TabNote extends StemmableNote {
this.ghost = false;
this.updateWidth();
}
// Return the number of the greatest string, which is the string lowest on the display
greatestString = (): number => {
return this.positions.map((x) => x.str).reduce((a, b) => a > b ? a : b);
}
// Return the number of the least string, which is the string highest on the display
leastString = (): number => {
return this.positions.map((x) => x.str).reduce((a, b) => a < b ? a : b);
}

reset(): this {
super.reset();
Expand Down
Loading

0 comments on commit c3365ff

Please sign in to comment.