diff --git a/packages/alphatab/src/rendering/glyphs/TabBeatContainerGlyph.ts b/packages/alphatab/src/rendering/glyphs/TabBeatContainerGlyph.ts index 437899ea0..2c5d9fd41 100644 --- a/packages/alphatab/src/rendering/glyphs/TabBeatContainerGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/TabBeatContainerGlyph.ts @@ -66,12 +66,17 @@ export class TabBeatContainerGlyph extends BeatContainerGlyph { } } if (!expanded) { + let slurText: string | undefined = undefined; + if (n.isHammerPullOrigin && n.hammerPullDestination) { + slurText = n.hammerPullDestination.fret >= n.fret ? 'H' : 'P'; + } const effectSlur: TabSlurGlyph = new TabSlurGlyph( `tab.slur.effect.${n.id}`, n, n.effectSlurDestination, false, - false + false, + slurText ); this._effectSlurs.push(effectSlur); this.addTie(effectSlur); diff --git a/packages/alphatab/src/rendering/glyphs/TabSlurGlyph.ts b/packages/alphatab/src/rendering/glyphs/TabSlurGlyph.ts index 97ba66030..88935e421 100644 --- a/packages/alphatab/src/rendering/glyphs/TabSlurGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/TabSlurGlyph.ts @@ -7,16 +7,22 @@ import { BeamDirection } from '@coderline/alphatab/rendering/utils/BeamDirection */ export class TabSlurGlyph extends TabTieGlyph { private _forSlide: boolean; + private readonly _slurText?: string; - public constructor(slurEffectId: string, startNote: Note, endNote: Note, forSlide: boolean, forEnd:boolean) { + public constructor(slurEffectId: string, startNote: Note, endNote: Note, forSlide: boolean, forEnd:boolean, slurText?: string) { super(slurEffectId, startNote, endNote, forEnd); this._forSlide = forSlide; + this._slurText = slurText; } public override getTieHeight(startX: number, _startY: number, endX: number, _endY: number): number { return (Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2; } + protected override getSlurText(): string | undefined { + return this._slurText; + } + public tryExpand(startNote: Note, endNote: Note, forSlide: boolean, forEnd: boolean): boolean { // same type required if (this._forSlide !== forSlide) { diff --git a/packages/alphatab/src/rendering/glyphs/TieGlyph.ts b/packages/alphatab/src/rendering/glyphs/TieGlyph.ts index 5b96249a0..8a0cf8119 100644 --- a/packages/alphatab/src/rendering/glyphs/TieGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/TieGlyph.ts @@ -165,6 +165,8 @@ export abstract class TieGlyph extends Glyph implements ITieGlyph { return; } + const isDown = this.tieDirection === BeamDirection.Down; + if (this.shouldDrawBendSlur()) { TieGlyph.drawBendSlur( canvas, @@ -172,7 +174,7 @@ export abstract class TieGlyph extends Glyph implements ITieGlyph { cy + this._startY, cx + this._endX, cy + this._endY, - this.tieDirection === BeamDirection.Down, + isDown, this.renderer.smuflMetrics.tieHeight ); } else { @@ -183,11 +185,31 @@ export abstract class TieGlyph extends Glyph implements ITieGlyph { cy + this._startY, cx + this._endX, cy + this._endY, - this.tieDirection === BeamDirection.Down, + isDown, this._tieHeight, this.renderer.smuflMetrics.tieMidpointThickness ); } + + const slurText = this.getSlurText(); + if (slurText) { + const midX = cx + (this._startX + this._endX) / 2; + const midY = cy + (this._startY + this._endY) / 2; + const apexOffset = this._tieHeight * 0.75; + const apexY = midY + (isDown ? apexOffset : -apexOffset); + const w = canvas.measureText(slurText).width; + const fontSize = canvas.font.size; + // text above: fontSize already includes descender space below the baseline, + // providing natural padding for capital letters like H/P + const textY = isDown + ? apexY + fontSize * 0.3 + : apexY - fontSize * 1.05; + canvas.fillText(slurText, midX - w / 2, textY); + } + } + + protected getSlurText(): string | undefined { + return undefined; } protected abstract shouldDrawBendSlur(): boolean;