From 3ce5a5c8e361813f2880aebf315b72418ba646c7 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Tue, 7 Jul 2026 18:43:15 +0900 Subject: [PATCH] fix(reader): center the lone PDF page in portrait auto-spread (#4984) (#4992) In auto-spread mode a portrait viewport shows a single page of the two-page spread, but the fixed-layout renderer kept the spread-centering one-sided inline margin on the shown page. With no partner page to meet at the spine, that margin stranded the lone page in one half of the viewport whenever it was narrower than the viewport (any zoom below 100%, or a page whose fit-scaled width is less than the viewport width), which also pushed the page over a page-turn tap zone so every tap turned the page instead of opening the menu. Bump the foliate-js submodule (readest/foliate-js#50) to center the lone portrait page and add a regression test for computeSpreadInlineMargins. Fixes #4984 Co-authored-by: Claude Opus 4.8 (1M context) --- .../fixed-layout-portrait-single-page.test.ts | 58 +++++++++++++++++++ packages/foliate-js | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 apps/readest-app/src/__tests__/document/fixed-layout-portrait-single-page.test.ts diff --git a/apps/readest-app/src/__tests__/document/fixed-layout-portrait-single-page.test.ts b/apps/readest-app/src/__tests__/document/fixed-layout-portrait-single-page.test.ts new file mode 100644 index 00000000..17cb6c2d --- /dev/null +++ b/apps/readest-app/src/__tests__/document/fixed-layout-portrait-single-page.test.ts @@ -0,0 +1,58 @@ +// Regression test for readest/readest issue #4984. +// +// In auto-spread mode a portrait viewport shows only one page of the two-page +// spread. The renderer already hides the other page and scales the shown page +// as a single page, but it kept the spread-centering inline margin: a left page +// gets `margin-inline-start: auto` (which pushes it toward the spine on the +// right) and a right page gets `margin-inline-end: auto` (pushing it left). With +// only one page visible and no partner to meet, that one-sided auto margin +// strands the page in one half of the viewport whenever it is narrower than the +// viewport (any zoom below 100%, or a page whose fit-scaled width is less than +// the viewport width). It also breaks tapping: the off-center page sits over a +// page-turn tap zone instead of the center menu zone, so every tap turns the +// page. +// +// The fix: in portrait, center the lone visible page (both inline margins auto) +// instead of the one-sided margin. In landscape the two pages keep their +// one-sided margins so they meet at the spine and the pair stays centered. +// `computeSpreadInlineMargins(portrait)` returns the inline-margin style for the +// left and right pages. It sets both inline margins explicitly so a re-render +// after an orientation change fully overwrites the previous layout's margins +// (frames are re-styled in place, not recreated, on rotation). + +import { describe, expect, it } from 'vitest'; + +import { computeSpreadInlineMargins } from 'foliate-js/fixed-layout.js'; + +describe('computeSpreadInlineMargins (#4984)', () => { + it('pushes each page toward the spine in landscape so the pair stays centered', () => { + const { left, right } = computeSpreadInlineMargins(false); + // Left page hugs the right (spine) edge; right page hugs the left edge. + expect(left.marginInlineStart).toBe('auto'); + expect(left.marginInlineEnd).not.toBe('auto'); + expect(right.marginInlineEnd).toBe('auto'); + expect(right.marginInlineStart).not.toBe('auto'); + }); + + it('centers the lone visible page in portrait instead of stranding it to a side', () => { + const { left, right } = computeSpreadInlineMargins(true); + // Whichever page of the spread is shown, its margins are symmetric so it is + // centered rather than shoved into one half of the viewport. + for (const style of [left, right]) { + expect(style.marginInlineStart).toBe('auto'); + expect(style.marginInlineEnd).toBe('auto'); + } + }); + + it('always sets both inline margins so an orientation change fully re-lays-out', () => { + // Both modes must define both sides; otherwise a stale `auto` left over from + // the previous orientation lingers and mis-centers the page. + for (const portrait of [false, true]) { + const { left, right } = computeSpreadInlineMargins(portrait); + for (const style of [left, right]) { + expect(style).toHaveProperty('marginInlineStart'); + expect(style).toHaveProperty('marginInlineEnd'); + } + } + }); +}); diff --git a/packages/foliate-js b/packages/foliate-js index 7c9cc45b..f6dced2a 160000 --- a/packages/foliate-js +++ b/packages/foliate-js @@ -1 +1 @@ -Subproject commit 7c9cc45bafb8b56934b623186bc1c7dbb6a848a2 +Subproject commit f6dced2aa0ce21506029af771f66bc02a0bf14e3