Skip to content

Commit fc3c3f4

Browse files
leocaseiroclaude
andcommitted
fix: scale horizontal layout width with display.scale
HorizontalScreenLayout scaled this.height by display.scale but left this.width in unscaled layout units. ScoreRenderer publishes that value as renderFinished.totalWidth, which on web sizes the overflow:hidden surface element, so with a scale above 1 everything past 1/scale of the score was clipped and unreachable by scrolling. Keep this.width in scaled units for the whole layout pass like the vertical layouts do, and use scaledWidth for the per-partial totalWidth. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 212f2ec commit fc3c3f4

2 files changed

Lines changed: 69 additions & 2 deletions

File tree

‎packages/alphatab/src/rendering/layout/HorizontalScreenLayout.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ export class HorizontalScreenLayout extends ScoreLayout {
119119
this._finalizeStaffSystem();
120120

121121
this.height = Math.floor(this._system.y + this._system.height);
122-
this.width = this._system.x + this._system.width + this.pagePadding![2];
122+
this.width =
123+
(this._system.x + this._system.width + this.pagePadding![2]) * this.renderer.settings.display.scale;
123124
currentBarIndex = 0;
124125

125126
let x = 0;
@@ -130,7 +131,7 @@ export class HorizontalScreenLayout extends ScoreLayout {
130131
e.reuseViewport = renderHints?.reuseViewport ?? false;
131132
e.x = x;
132133
e.y = 0;
133-
e.totalWidth = this.width;
134+
e.totalWidth = this.scaledWidth;
134135
e.totalHeight = this.height;
135136
e.width = partial.width;
136137
e.height = this.height;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { AlphaTexImporter } from '@coderline/alphatab/importer/AlphaTexImporter';
2+
import { ByteBuffer } from '@coderline/alphatab/io/ByteBuffer';
3+
import { LayoutMode } from '@coderline/alphatab/LayoutMode';
4+
import type { Score } from '@coderline/alphatab/model/Score';
5+
import type { RenderFinishedEventArgs } from '@coderline/alphatab/rendering/RenderFinishedEventArgs';
6+
import { ScoreRenderer } from '@coderline/alphatab/rendering/ScoreRenderer';
7+
import { Settings } from '@coderline/alphatab/Settings';
8+
import { describe, expect, it } from 'vitest';
9+
10+
describe('HorizontalScreenLayoutScale', () => {
11+
// more bars than display.barCountPerPartial so that multiple partials are produced
12+
const tex = `\\title "scale"\n.${new Array(24).fill('3.3.4*4').join(' | ')}`;
13+
14+
function createSettings(scale: number): Settings {
15+
const settings = new Settings();
16+
settings.core.enableLazyLoading = true;
17+
settings.display.layoutMode = LayoutMode.Horizontal;
18+
settings.display.scale = scale;
19+
// the page padding is scale independent, without it the totals scale exactly
20+
settings.display.padding = [0, 0, 0, 0];
21+
return settings;
22+
}
23+
24+
function loadScore(settings: Settings): Score {
25+
const importer = new AlphaTexImporter();
26+
importer.init(ByteBuffer.fromString(tex), settings);
27+
return importer.readScore();
28+
}
29+
30+
function render(scale: number): [RenderFinishedEventArgs, RenderFinishedEventArgs[]] {
31+
const settings = createSettings(scale);
32+
const renderer = new ScoreRenderer(settings);
33+
renderer.width = 1300;
34+
35+
const partials: RenderFinishedEventArgs[] = [];
36+
let total: RenderFinishedEventArgs | undefined;
37+
renderer.partialLayoutFinished.on(e => partials.push(e));
38+
renderer.renderFinished.on(e => {
39+
total = e;
40+
});
41+
renderer.error.on(e => {
42+
throw e;
43+
});
44+
renderer.renderScore(loadScore(settings), [0]);
45+
46+
expect(total).toBeDefined();
47+
expect(partials.length).toBeGreaterThan(1);
48+
return [total!, partials];
49+
}
50+
51+
it('scales the reported total width like the total height', () => {
52+
const [unscaled] = render(1);
53+
const [scaled] = render(2);
54+
55+
expect(scaled.totalWidth).toBeCloseTo(unscaled.totalWidth * 2, 0);
56+
expect(scaled.totalHeight).toBeCloseTo(unscaled.totalHeight * 2, 0);
57+
});
58+
59+
it('reports the same total width on the partials as on the final result', () => {
60+
const [total, partials] = render(2);
61+
62+
for (const partial of partials) {
63+
expect(Math.abs(partial.totalWidth - total.totalWidth)).toBeLessThanOrEqual(2);
64+
}
65+
});
66+
});

0 commit comments

Comments
 (0)