diff --git a/src/annotation.ts b/src/annotation.ts index dd7d8f57ff..bef8b9adb2 100644 --- a/src/annotation.ts +++ b/src/annotation.ts @@ -8,9 +8,8 @@ import { Stave } from './stave'; import { Stem } from './stem'; import { StemmableNote } from './stemmablenote'; import { Tables } from './tables'; -import { TabNote } from './tabnote'; import { TextFormatter } from './textformatter'; -import { Category } from './typeguard'; +import { Category, isStemmableNote, isTabNote } from './typeguard'; import { log } from './util'; // eslint-disable-next-line @@ -86,7 +85,8 @@ export class Annotation extends Modifier { const stemDirection = note.hasStem() ? note.getStemDirection() : Stem.UP; let stemHeight = 0; let lines = 5; - if (note instanceof TabNote) { + + if (isTabNote(note)) { if (note.render_options.draw_stem) { const stem = (note as StemmableNote).getStem(); if (stem) { @@ -95,8 +95,8 @@ export class Annotation extends Modifier { } else { stemHeight = 0; } - } else if (note instanceof StemmableNote) { - const stem = (note as StemmableNote).getStem(); + } else if (isStemmableNote(note)) { + const stem = note.getStem(); if (stem && note.getNoteType() === 'n') { stemHeight = Math.abs(stem.getHeight()) / Tables.STAVE_LINE_DISTANCE; } @@ -110,8 +110,8 @@ export class Annotation extends Modifier { if (annotation.verticalJustification === this.VerticalJustify.TOP) { let noteLine = note.getLineNumber(true); - if (note instanceof TabNote) { - noteLine = lines - ((note as TabNote).leastString() - 0.5); + if (isTabNote(note)) { + noteLine = lines - (note.leastString() - 0.5); } if (stemDirection === Stem.UP) { noteLine += stemHeight; @@ -127,8 +127,8 @@ export class Annotation extends Modifier { } } else if (annotation.verticalJustification === this.VerticalJustify.BOTTOM) { let noteLine = lines - note.getLineNumber(); - if (note instanceof TabNote) { - noteLine = (note as TabNote).greatestString() - 1; + if (isTabNote(note)) { + noteLine = note.greatestString() - 1; } if (stemDirection === Stem.DOWN) { noteLine += stemHeight; diff --git a/src/articulation.ts b/src/articulation.ts index 972e5a0d10..3ad83faaf4 100644 --- a/src/articulation.ts +++ b/src/articulation.ts @@ -11,7 +11,7 @@ import { Stave } from './stave'; import { Stem } from './stem'; import { StemmableNote } from './stemmablenote'; import { Tables } from './tables'; -import { Category, isGraceNote, isStaveNote, isTabNote } from './typeguard'; +import { Category, isGraceNote, isStaveNote, isStemmableNote, isTabNote } from './typeguard'; import { defined, log, RuntimeError } from './util'; export interface ArticulationStruct { @@ -218,8 +218,9 @@ export class Articulation extends Modifier { 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 (isStemmableNote(note)) { + const stem = note.getStem(); if (stem) { stemHeight = Math.abs(stem.getHeight()) / Tables.STAVE_LINE_DISTANCE; } diff --git a/src/bend.ts b/src/bend.ts index d3a8fec5dc..af8c57b973 100644 --- a/src/bend.ts +++ b/src/bend.ts @@ -6,8 +6,7 @@ import { FontInfo } from './font'; import { Modifier } from './modifier'; import { ModifierContextState } from './modifiercontext'; import { Tables } from './tables'; -import { TabNote } from './tabnote'; -import { Category } from './typeguard'; +import { Category, isTabNote } from './typeguard'; import { RuntimeError } from './util'; export interface BendPhrase { @@ -44,8 +43,9 @@ export class Bend extends Modifier { 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 (isTabNote(note)) { + const stringPos = note.leastString() - 1; if (state.top_text_line < stringPos) { state.top_text_line = stringPos; } diff --git a/src/ghostnote.ts b/src/ghostnote.ts index 43a2edadb8..b904c4c818 100644 --- a/src/ghostnote.ts +++ b/src/ghostnote.ts @@ -2,12 +2,11 @@ // // ## Description -import { Annotation } from './annotation'; import { ModifierContext } from './modifiercontext'; import { NoteStruct } from './note'; import { Stave } from './stave'; import { StemmableNote } from './stemmablenote'; -import { Category } from './typeguard'; +import { Category, isAnnotation } from './typeguard'; import { RuntimeError } from './util'; const ERROR_MSG = 'Ghost note must have valid initialization data to identify duration.'; @@ -67,7 +66,7 @@ export class GhostNote extends StemmableNote { this.setRendered(); for (let i = 0; i < this.modifiers.length; ++i) { const modifier = this.modifiers[i]; - if (modifier instanceof Annotation) { + if (isAnnotation(modifier)) { modifier.setContext(this.getContext()); modifier.drawWithStyle(); } diff --git a/src/renderer.ts b/src/renderer.ts index 8a45a42b49..5457d4665b 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -4,6 +4,7 @@ import { CanvasContext } from './canvascontext'; import { RenderContext } from './rendercontext'; import { SVGContext } from './svgcontext'; +import { isRenderContext } from './typeguard'; import { RuntimeError } from './util'; import { isHTMLCanvas, isHTMLDiv } from './web'; @@ -112,10 +113,8 @@ export class Renderer { constructor(context: RenderContext); constructor(canvas: string | HTMLCanvasElement | HTMLDivElement, backend: number); constructor(arg0: string | HTMLCanvasElement | HTMLDivElement | RenderContext, arg1?: number) { - if (arg0 instanceof RenderContext) { + if (isRenderContext(arg0)) { // The user has provided what looks like a RenderContext, let's just use it. - // TODO(tommadams): RenderContext is an interface, can we introduce a context base class - // to make this check more robust? this.ctx = arg0; } else { if (arg1 === undefined) { diff --git a/src/typeguard.ts b/src/typeguard.ts index 312bc01c1b..0b86bd7d87 100644 --- a/src/typeguard.ts +++ b/src/typeguard.ts @@ -2,19 +2,19 @@ // Author: Ron B. Yeh // MIT License -import { Accidental } from './accidental.js'; -import { Dot } from './dot.js'; -import { GraceNote } from './gracenote.js'; -import { GraceNoteGroup } from './gracenotegroup.js'; -import { Note } from './note.js'; -import { Barline } from './stavebarline.js'; -import { StaveNote } from './stavenote.js'; -import { StemmableNote } from './stemmablenote.js'; -import { TabNote } from './tabnote.js'; +import { Accidental } from './accidental'; +import { Annotation } from './annotation'; +import { Dot } from './dot'; +import { GraceNote } from './gracenote'; +import { GraceNoteGroup } from './gracenotegroup'; +import { Note } from './note'; +import { RenderContext } from './rendercontext.js'; +import { Barline } from './stavebarline'; +import { StaveNote } from './stavenote'; +import { StemmableNote } from './stemmablenote'; +import { TabNote } from './tabnote'; /* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -/* eslint-disable @typescript-eslint/ban-types */ /** * Use instead of `instanceof` as a more flexible type guard. @@ -52,15 +52,17 @@ export function isCategory(obj: any, category: string, checkAncestors: boolea } } -export const isStemmableNote = (obj: unknown): obj is StemmableNote => isCategory(obj, Category.StemmableNote); -export const isStaveNote = (obj: unknown): obj is StaveNote => isCategory(obj, Category.StaveNote); -export const isBarline = (obj: unknown): obj is Barline => isCategory(obj, Category.Barline); export const isAccidental = (obj: unknown): obj is Accidental => isCategory(obj, Category.Accidental); +export const isAnnotation = (obj: unknown): obj is Annotation => isCategory(obj, Category.Annotation); +export const isBarline = (obj: unknown): obj is Barline => isCategory(obj, Category.Barline); +export const isDot = (obj: unknown): obj is Dot => isCategory(obj, Category.Dot); export const isGraceNote = (obj: unknown): obj is GraceNote => isCategory(obj, Category.GraceNote); export const isGraceNoteGroup = (obj: unknown): obj is GraceNoteGroup => isCategory(obj, Category.GraceNoteGroup); -export const isTabNote = (obj: unknown): obj is TabNote => isCategory(obj, Category.TabNote); -export const isDot = (obj: unknown): obj is Dot => isCategory(obj, Category.Dot); export const isNote = (obj: unknown): obj is Note => isCategory(obj, Category.Note); +export const isRenderContext = (obj: unknown): obj is RenderContext => isCategory(obj, Category.RenderContext); +export const isStaveNote = (obj: unknown): obj is StaveNote => isCategory(obj, Category.StaveNote); +export const isStemmableNote = (obj: unknown): obj is StemmableNote => isCategory(obj, Category.StemmableNote); +export const isTabNote = (obj: unknown): obj is TabNote => isCategory(obj, Category.TabNote); // 'const' enums are erased by the TypeScript compiler. The string values are inlined at all the use sites. // See: https://www.typescriptlang.org/docs/handbook/enums.html#const-enums @@ -97,6 +99,7 @@ export const enum Category { Ornament = 'Ornament', Parenthesis = 'Parenthesis', PedalMarking = 'PedalMarking', + RenderContext = 'RenderContext', RepeatNote = 'RepeatNote', Repetition = 'Repetition', Stave = 'Stave', diff --git a/tests/typeguard_tests.ts b/tests/typeguard_tests.ts index 85219f3811..8a0ab4765f 100644 --- a/tests/typeguard_tests.ts +++ b/tests/typeguard_tests.ts @@ -37,6 +37,12 @@ function real(): void { ok(isNote(s), 'StaveNote extends StemmableNote which extends Note, so s is a Note'); ok(isStemmableNote(t), 'TabNote extends StemmableNote'); ok(isNote(t), 'TabNote extends StemmableNote which extends Note, so t is a Note'); + + const canvas = document.createElement('canvas'); + canvas.width = 800; + canvas.height = 400; + const ctx = new CanvasContext(canvas.getContext('2d') as CanvasRenderingContext2D); + ok(isRenderContext(ctx), 'ctx is a RenderContext'); } /**