diff --git a/packages/alphatab/src/rendering/layout/HorizontalScreenLayout.ts b/packages/alphatab/src/rendering/layout/HorizontalScreenLayout.ts index 5e23de668..f284e1fa5 100644 --- a/packages/alphatab/src/rendering/layout/HorizontalScreenLayout.ts +++ b/packages/alphatab/src/rendering/layout/HorizontalScreenLayout.ts @@ -119,7 +119,8 @@ export class HorizontalScreenLayout extends ScoreLayout { this._finalizeStaffSystem(); this.height = Math.floor(this._system.y + this._system.height); - this.width = this._system.x + this._system.width + this.pagePadding![2]; + this.width = + (this._system.x + this._system.width + this.pagePadding![2]) * this.renderer.settings.display.scale; currentBarIndex = 0; let x = 0; @@ -130,7 +131,7 @@ export class HorizontalScreenLayout extends ScoreLayout { e.reuseViewport = renderHints?.reuseViewport ?? false; e.x = x; e.y = 0; - e.totalWidth = this.width; + e.totalWidth = this.scaledWidth; e.totalHeight = this.height; e.width = partial.width; e.height = this.height; diff --git a/packages/alphatab/test/rendering/HorizontalScreenLayoutScale.test.ts b/packages/alphatab/test/rendering/HorizontalScreenLayoutScale.test.ts new file mode 100644 index 000000000..ca0c8b2c9 --- /dev/null +++ b/packages/alphatab/test/rendering/HorizontalScreenLayoutScale.test.ts @@ -0,0 +1,66 @@ +import { AlphaTexImporter } from '@coderline/alphatab/importer/AlphaTexImporter'; +import { ByteBuffer } from '@coderline/alphatab/io/ByteBuffer'; +import { LayoutMode } from '@coderline/alphatab/LayoutMode'; +import type { Score } from '@coderline/alphatab/model/Score'; +import type { RenderFinishedEventArgs } from '@coderline/alphatab/rendering/RenderFinishedEventArgs'; +import { ScoreRenderer } from '@coderline/alphatab/rendering/ScoreRenderer'; +import { Settings } from '@coderline/alphatab/Settings'; +import { describe, expect, it } from 'vitest'; + +describe('HorizontalScreenLayoutScale', () => { + // more bars than display.barCountPerPartial so that multiple partials are produced + const tex = `\\title "scale"\n.${new Array(24).fill('3.3.4*4').join(' | ')}`; + + function createSettings(scale: number): Settings { + const settings = new Settings(); + settings.core.enableLazyLoading = true; + settings.display.layoutMode = LayoutMode.Horizontal; + settings.display.scale = scale; + // the page padding is scale independent, without it the totals scale exactly + settings.display.padding = [0, 0, 0, 0]; + return settings; + } + + function loadScore(settings: Settings): Score { + const importer = new AlphaTexImporter(); + importer.init(ByteBuffer.fromString(tex), settings); + return importer.readScore(); + } + + function render(scale: number): [RenderFinishedEventArgs, RenderFinishedEventArgs[]] { + const settings = createSettings(scale); + const renderer = new ScoreRenderer(settings); + renderer.width = 1300; + + const partials: RenderFinishedEventArgs[] = []; + let total: RenderFinishedEventArgs | undefined; + renderer.partialLayoutFinished.on(e => partials.push(e)); + renderer.renderFinished.on(e => { + total = e; + }); + renderer.error.on(e => { + throw e; + }); + renderer.renderScore(loadScore(settings), [0]); + + expect(total).toBeDefined(); + expect(partials.length).toBeGreaterThan(1); + return [total!, partials]; + } + + it('scales the reported total width like the total height', () => { + const [unscaled] = render(1); + const [scaled] = render(2); + + expect(scaled.totalWidth).toBeCloseTo(unscaled.totalWidth * 2, 0); + expect(scaled.totalHeight).toBeCloseTo(unscaled.totalHeight * 2, 0); + }); + + it('reports the same total width on the partials as on the final result', () => { + const [total, partials] = render(2); + + for (const partial of partials) { + expect(Math.abs(partial.totalWidth - total.totalWidth)).toBeLessThanOrEqual(2); + } + }); +});