Skip to content

feat: two-page EPUB spreads on unfolded foldables and landscape tablets (refs #286) - #300

Merged
van-geaux merged 5 commits into
van-geaux:mainfrom
chortya:up/feat-epub-two-page-spreads
Oct 20, 2026
Merged

van-geaux merged 5 commits into
van-geaux:mainfrom
chortya:up/feat-epub-two-page-spreads

Conversation

@chortya

@chortya chortya commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Contribution pause

The maintainer lifted the pause and invited split PRs in #286.

  • The change is scoped to one feature (two-page EPUB spreads on large horizontal windows). Comic spreads, the OLED theme and typesetting are separate PRs.
  • Compatible with the provider-module refactor: reader-only, no provider or repository code touched.
  • Tests and manual-validation status are documented.
  • No credentials, cookies, API keys, private server URLs, or generated local data are included.

Summary

On an unfolded foldable (e.g. Galaxy Z Fold) or a landscape tablet, paginated EPUB shows two pages side by side, like a printed book. Portrait, the folded cover screen, split-screen and continuous layout stay single-page.

Piece What it does
ReaderSpreadPolicy Pure decision: paginated EPUB, setting on, landscape window whose short side is ≥ 600dp. Window size, not display size, is used, so split-screen falls back to one page; a half-opened tabletop posture keeps one page.
ReaderWindowTracker Activity configuration plus Jetpack WindowManager FoldingFeature posture, debounced; collected only while the reader is started, re-checked on each start.
Readium configuration columnCount = TWO / fixed-layout Spread.ALWAYS, plus inline RsProperties (--RS__colCount: 2, --RS__colWidth: auto, --RS__pageGutter). The inline properties bypass ReadiumCSS's 60em two-column media query, which Fold inner displays do not reach.
Margins Each page gets the margin the left/right setting gives a single page on that window (mean of left and right, 12px minimum, capped at an eighth of the window so each page keeps half its width). Changes apply live through submitPreferences. Fixed-layout spreads keep the normal container margins.
Recreation A posture or preference change that flips the decision without a size change recreates the reader at the saved locator, because Readium CSS properties are fixed at navigator creation.
Setting Two-page spread when unfolded, per library, on by default, in the reader options (EPUB layout section) and the library reader settings. Stored with the existing library reader profile; older profiles default to on.

New dependencies: androidx.window:window and window-java 1.5.1.

Verification

  • ./gradlew :app:testDebugUnitTest — 971/971 pass (new: ReaderSpreadPolicyTest, EpubSpreadPreferencesTest, spread cases in LibraryReaderPreferencesTest). :app:lintDebug — 0 errors. :app:assembleDebug succeeds. No added trailing whitespace; line endings preserved.
  • Connected, Pixel Fold API 36 emulator unfolded (2208×1840):
    • New EpubSpreadActivityInstrumentedTest 5/5 — two columns on the unfolded window and one otherwise, setting off keeps one column, all four page margins equal and following 15% / 40% / uneven / 100% (capped) settings live, line spacing and font size identical on both pages, rotate to portrait returns to one column.
    • New EpubSpreadWebViewInstrumentedTest 2/2 — the column properties against the shipped ReadiumCSS below 60em.
    • ReadiumEpubOpenInstrumentedTest 20/22 (new spread preference cases pass). The 2 failures (selectsARealReadiumPositionForNonzeroServerPercentage, generatedEpubImageColorOverrideIsUniversalAndIdempotent) also fail on main on this emulator. centerTapOpensLightweightReaderChrome failed once and passed on rerun.
    • EpubReaderOptionsOverlayInstrumentedTest 3/8; the same 5 fail on main on this emulator.
  • Device: the same feature has been in daily use on a Galaxy Z Fold8 from the fork build. This exact branch (split for upstream, with the review fixes below) has only been run on the emulator.
  • Not covered by an automated test: fixed-layout EPUB spreads (Readium's own Spread.ALWAYS), posture changes on real hinge hardware.
  • Peer review (Codex GPT-6-Astra) flagged:
    • margins collapsing the text at the 100% setting → capped;
    • fixed-layout books losing their margins in a spread → fixed;
    • vertical CJK text (raised twice) → not applicable: readiumEpubReadingProgression maps both library reading directions to Readium LTR (pinned by an existing LibraryReaderPreferencesTest), and Readium 3.0.2 enables vertical text only for a CJK language and RTL progression, so vertical text cannot occur in this app today. If RTL progression is ever passed through, the spread should be gated on Readium's resolved verticalText.

🤖 Generated with Claude Code

Andriy Chortkivskyy and others added 5 commits October 18, 2026 19:55
Add a geometry-based spread policy (sw>=600dp, horizontal, paginated,
not tabletop) backed by Jetpack WindowManager posture, per-library
spread preferences with options UI, and Readium two-column/fixed-layout
spread configuration that bypasses ReadiumCSS's 60em media query.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each spread page now gets the same margin on its outer and inner side,
sized like the single-page margin for the same setting, and changes to
margins apply live. The container drops its horizontal padding in a
spread; Readium's per-column gutter is a 1px unit scaled by pageMargins.

Tests: JVM margin mapping; on-device measurement of all four page
margins, line pitch on both pages under line-spacing and font-size
changes, and the rotate-to-portrait fallback for EPUB and comics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed; EPUB-only scope

- ReaderWindowTracker.observeSpreadChanges collects spread decisions under
  repeatOnLifecycle(STARTED) and re-checks on each start, so a debounced
  posture change cannot recreate the activity after state is saved and a
  change made while hidden is applied on return.
- Drop comic-only spread settings from this change; comic spreads are a
  separate PR.
- EpubSpreadActivityInstrumentedTest: two columns vs one, setting off,
  symmetric margins following the margin setting, line spacing and font
  size on both pages, rotate-to-portrait fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The spread page margin is capped at an eighth of the window, so each
  page keeps at least half its width for text at the largest margin
  settings instead of collapsing to nothing.
- Fixed-layout spreads are drawn by Readium and do not use the reflowable
  column padding, so they keep the normal container margins.
- EpubSpreadWebViewInstrumentedTest checks the column properties against
  the shipped ReadiumCSS below its 60em breakpoint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hinge gutter helper is used only by comic spreads and moves to that
change; the unused window-testing dependency is dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@van-geaux

Copy link
Copy Markdown
Owner

Thanks @chortya, this looks good. The EPUB spread behavior is covered by the new tests, CI is passing, and I found no further changes needed. The PR is ready to merge.

@van-geaux
van-geaux merged commit 519c380 into van-geaux:main Oct 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants