Skip to content

🐛 Respect spine page-progression-direction in Readium manifests - #1438

Merged
aaronleopold merged 1 commit into
stumpapp:nightlyfrom
omegaatt36:fix/spine-reading-progression
Sep 30, 2026
Merged

aaronleopold merged 1 commit into
stumpapp:nightlyfrom
omegaatt36:fix/spine-reading-progression

Conversation

@omegaatt36

@omegaatt36 omegaatt36 commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Description

ReadiumManifestGenerator::extract_metadata resolved readingProgression via get_first("direction"), i.e. it looked for a direction item in the OPF <metadata>. That key never exists on standard books: page-progression-direction is an attribute on the <spine> element, and the epub crate (stumpapp/epub-rs @ baf89d1) never parses it (it only reads the spine's toc attribute). The lookup therefore almost always fell back to "ltr", and EPUB manifests were published with readingProgression: "ltr".

Why this matters

@readium/navigator's getScriptMode() only selects its CJK vertical pipeline (cjk-vertical — vertical ReadiumCSS, noVerticalPagination, forced scrolled layout with CJKVerticalSnapper, and the EBPAJ fonts patch for books carrying ebpaj:guide-version) when the manifest reports rtl alongside a CJK primary language.

With the manifest saying ltr:

  • Japanese/Chinese vertical EPUBs (EBPAJ-style exports, e.g. from Google Play Books) rendered through the horizontal pipeline. In Chromium the books' own prefixed CSS (-webkit-writing-mode: vertical-rl) masked this partially, producing the symptoms described in [BUG] Issues with Japanese epubs #723 (lines aligned to the bottom, punctuation on the wrong side); in Firefox, which supports neither -epub-writing-mode nor -webkit-writing-mode, vertical books fell back to fully horizontal text.

After this change the vertical pipeline activates and vertical EPUBs render correctly in both browsers.

Changes

  • Parse page-progression-direction from the OPF <spine> directly (epub.get_resource_str_by_path(root_file) + quick-xml), matching on local_name() so prefixed variants like <opf:spine> are handled identically
  • Spine values win over the pre-existing legacy <meta name="direction"> fallback. Values other than ltr/rtl (e.g. EPUB 3's "default") don't specify a direction: they fall through to the legacy meta if present, then to "ltr"
  • 8 new unit tests, including a minimal RTL EPUB generated at test time (prefixed and unprefixed spines, legacy meta fallback and priority, default with and without a legacy meta); the existing fixture is asserted to remain "ltr"

Verification

  • cargo test -p stump_core --lib: 216 passed (15 in readium::generator); cargo clippy -- -D warnings clean
  • Real-world book (zh-TW light novel, EBPAJ guide 1.1.2, page-progression-direction="rtl"): manifest now reports "rtl", and the reader renders vertical text correctly in both Firefox (Zen) and Chrome
圖片 圖片

Notes

  • Related to [BUG] Issues with Japanese epubs #723 — this addresses the remaining writing-mode/layout half (the direction half was fixed in 🐛 Fix Japanese epub text direction #740). I deliberately did not mark it as closing [BUG] Issues with Japanese epubs #723, since that issue may deserve a broader follow-up discussion.
  • Follow-up observation: Stump's manifests currently don't emit conformsTo/rendition:layout metadata, so FXL publications are treated as reflowable by the Readium navigator. For ja/zh + rtl fixed-layout books (most manga), the new cjk-vertical path is now reachable — worth testing with a manga EPUB. The proper fix belongs in a separate PR/issue (emitting layout metadata in the manifest).
  • I also considered a client-side CSS shim translating -epub-writing-mode/-webkit-writing-mode, but it is a symptom-level workaround (and fights the frame's connect-src 'none' CSP); fixing the manifest enables Readium's purpose-built pipeline instead.

LLM disclosure

Per the contributing guidelines: this contribution was developed with the help of an LLM coding assistant. I reviewed, tested and verified the final change myself (including running the server and reading the affected book in both Firefox and Chrome), and this PR body was written with its help. No commits are signed by the tool.

@omegaatt36
omegaatt36 force-pushed the fix/spine-reading-progression branch from 03dde3f to bd457c3 Compare September 22, 2026 08:32
@omegaatt36
omegaatt36 force-pushed the fix/spine-reading-progression branch 2 times, most recently from 58a94e3 to fb104b7 Compare September 22, 2026 09:01
@codecov

codecov Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
core/src/readium/generator.rs 96.78% <100.00%> (+1.28%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@omegaatt36
omegaatt36 force-pushed the fix/spine-reading-progression branch from fb104b7 to 689e046 Compare September 22, 2026 16:29
@omegaatt36

Copy link
Copy Markdown
Contributor Author

test after patch test with codecov

❯ cargo test -p stump_core --lib readium::generator
   Compiling stump_core v0.1.10 (/Users/raiven_kao/dev/stump/core)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 16.52s
warning: the following packages contain code that will be rejected by a future version of Rust: proc-macro-error2 v2.0.1
note: to see what the problems were, use the option `--future-incompat-report`, or run `cargo report future-incompatibilities --id 1`
     Running unittests src/lib.rs (target/debug/deps/stump_core-a053c2b7d38e8d26)

running 17 tests
test readium::generator::tests::test_manifest_serialization ... ok
test readium::generator::tests::test_manifest_serialization_nested_toc ... ok
test readium::generator::tests::test_existing_fixture_reading_progression_is_ltr ... ok
test readium::generator::tests::test_generate_manifest_from_fixture ... ok
test readium::generator::tests::test_resource_url_percent_encodes_segments ... ok
test readium::generator::tests::test_rwpm_link_builder ... ok
test readium::generator::tests::test_rwpm_link_children_builder ... ok
test readium::generator::tests::test_generate_positions_from_fixture ... ok
test readium::generator::tests::test_reading_progression_spine_wins_over_legacy_direction_meta ... ok
test readium::generator::tests::test_reading_progression_from_prefixed_spine_rtl ... ok
test readium::generator::tests::test_reading_progression_spine_default_falls_back_to_ltr ... ok
test readium::generator::tests::test_reading_progression_spine_default_uses_legacy_direction_meta ... ok
test readium::generator::tests::test_reading_progression_from_spine_rtl ... ok
test readium::generator::tests::test_reading_progression_falls_back_to_legacy_direction_meta ... ok
test readium::generator::tests::test_reading_progression_defaults_to_ltr_without_spine_attribute ... ok
test readium::generator::tests::test_spine_reading_progression_returns_none_when_scanned_document_has_no_spine ... ok
test readium::generator::tests::test_spine_reading_progression_returns_none_on_malformed_xml ... ok

test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 201 filtered out; finished in 0.01s

@aaronleopold

Copy link
Copy Markdown
Collaborator

Thank you, @omegaatt36! I'm hoping to have time to review this over the weekend

EPUB 3 declares reading direction on <spine page-progression-direction>,
but the manifest generator looked for a 'direction' metadata entry that
never exists on standard books. readingProgression was therefore always
published as 'ltr'.

Readium's web reader only enables its CJK vertical pipeline (vertical
ReadiumCSS, CJKVerticalSnapper, noVerticalPagination) when the manifest
reports 'rtl' alongside a CJK primary language, so vertical Japanese and
Chinese EPUBs fell back to horizontal layout in Firefox and to mismatched
pagination in Chrome (see stumpapp#723).

epub-rs parses only the spine's 'toc' attribute, so the OPF is read
directly from the archive. Spine values win over a legacy
<meta name="direction"> entry, and non-directional spine values
(e.g. 'default') fall through to the legacy meta, then to 'ltr'.

Refs stumpapp#723
@omegaatt36
omegaatt36 force-pushed the fix/spine-reading-progression branch from 689e046 to bd7cef3 Compare September 30, 2026 12:01

@aaronleopold aaronleopold left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@aaronleopold
aaronleopold enabled auto-merge (squash) September 30, 2026 14:20
@aaronleopold
aaronleopold merged commit ddb8788 into stumpapp:nightly Sep 30, 2026
6 checks 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