Skip to content

fix #2839: scale horizontal layout width with display.scale - #2894

Open
leocaseiro wants to merge 1 commit into
CoderLine:developfrom
leocaseiro:fix/issue-2839
Open

leocaseiro wants to merge 1 commit into
CoderLine:developfrom
leocaseiro:fix/issue-2839

Conversation

@leocaseiro

@leocaseiro leocaseiro commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Note

AI-authored disclosure (alphatab-ai-authored-v1)

Portions of this content were authored by an AI agent. The agent has read
AGENTS.md and the human submitter accepts responsibility for
compliance with the rules in that document.

Issues

Fixes #2839

Note on acceptance: #2839 is assigned to @Danielku15 but is not labelled state-accepted. I read the assignment as triage, since no open issue currently carries that label — if that is the wrong read, happy to park this until the issue is formally accepted.

Proposed changes

HorizontalScreenLayout.doLayoutAndRender scales this.height by display.scale at the end of the pass, but leaves this.width in unscaled layout units. ScoreRenderer publishes that value as renderFinished.totalWidth, and AlphaTabApiBase uses it to size the canvas element — on web that is the overflow: hidden .at-surface. With display.scale > 1 the surface ends up at 1/scale of the rendered content, so the tail of the score is clipped and unreachable by scrolling, and the playhead runs past the visible area.

This keeps this.width in scaled units for the whole layout pass, the way the vertical layouts do, so ScoreLayout.scaledWidth hands back the unscaled layout width where it is needed — including for the per-partial totalWidth, which VerticalLayoutBase already reports that way.

Measured in Chrome, both sides built from source with npm run build, 30 bars, layoutMode: Horizontal, scale: 2:

develop @ 212f2ec this branch
rendered content (sum of partial widths) 11833px 11833px
.at-surface width 5780px 11559px
scroll container scrollWidth 5780px 11559px

Scrolled all the way right, develop stops at bar 15 of 30; with the change the final barline is reached.

Smaller alternative

A single this.width *= this.renderer.settings.display.scale; next to the existing this.height *= ... line fixes the reported symptom identically — I measured both and the surface width and scroll range come out the same. I went with the version in this PR because it also keeps e.totalWidth correct on the per-partial partialLayoutFinished events: with the one-liner those stay a factor of scale too small, because this.width is still unscaled while layoutAndRenderBottomScoreInfo and _layoutAndRenderAnnotation run. Happy to switch to the one-liner if you would rather keep the diff minimal.

Root-cause analysis

Which layer is the cause? The layout layer, not the renderer or the web bindings. The partials are already positioned, sized and painted correctly at every scale — only the total reported for the whole layout is wrong. ScoreLayout.width is device-space for every other layout (PageViewLayout assigns this.renderer.width to it, and scaledWidth is defined as this.width / scale to convert back), and HorizontalScreenLayout is the one place that leaves it in layout units.

General defect or input-specific? Neither — it is a regression, and it affects every score in horizontal layout at scale != 1. It came in with abc38b9 (first released in v1.7.0), which moved the * scale on height to the end of doLayoutAndRender and dropped the one on width:

- this.height = Math.floor(this._system.y + this._system.height) * scale;
- this.width  = (this._system.x + this._system.width + this.pagePadding![2]) * scale;
+ this.height = Math.floor(this._system.y + this._system.height);
+ this.width  = (this._system.x + this._system.width + this.pagePadding![2]);
...
+ this.height *= this.renderer.settings.display.scale;

The height half of that commit is correct and untouched here; this restores the width half in the same shape.

Does the fix apply consistently across TS, .NET and Kotlin? Yes — the change is in the shared layout source, so it transpiles into the .NET and Kotlin outputs unchanged. I verified the behaviour on web only, since the overflow: hidden surface element is what makes it visible there; the totalWidth contract it fixes is the same on all three.

Do existing tests still hold, and what should be added? The full packages/alphatab suite is green locally — 79 files, 1760 tests, including the visual reference tests. The visual tests are unaffected because they run at display.scale 1, where * scale is a no-op. They also could not have caught this: they compare painted partials, and the partials were always correct. The gap was that nothing asserted the reported total, which is what embedders size their scroll surface with — so that is what the new test covers.

Checklist

  • I consent that this change becomes part of alphaTab under its current or any future open source license
  • This PR is linked to an accepted issue (see above)
  • Changes are implemented
  • New tests were added
  • I have read AGENTS.md if an AI helped draft any part of this PR

test/rendering/HorizontalScreenLayoutScale.test.ts renders the horizontal layout at scale 1 and 2 and asserts that the reported total width scales like the total height, and that every partial reports the same total width as the final result. Both assertions fail on develop (the width comes back at exactly half) and pass with this change.

AI authorship disclosure

  • No AI agent authored any part of this PR (description, code, tests, or commit messages)
  • An AI agent contributed to this PR. The AI-authored disclosure block (alphatab-ai-authored-v1) is present at the top of this body, and I have personally reviewed every change and can explain each one

Further details

  • This is a breaking change
  • This change will require update of the documentation/website

🤖 Generated with Claude Code

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>
@leocaseiro leocaseiro changed the title fix: scale horizontal layout width with display.scale fix #2839: scale horizontal layout width with display.scale Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Width of horizontal layout not working properly with scale larger that 1

1 participant