Skip to content

fix(reader): 移动端 useBookFonts 不应强制阅读器字体到 body - #756

Open
k6G52m4Dz75W wants to merge 3 commits into
codedogQBY:mainfrom
k6G52m4Dz75W:fix/mobile-usebookfonts-clean
Open

k6G52m4Dz75W wants to merge 3 commits into
codedogQBY:mainfrom
k6G52m4Dz75W:fix/mobile-usebookfonts-clean

Conversation

@k6G52m4Dz75W

@k6G52m4Dz75W k6G52m4Dz75W commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

问题

移动端阅读器(reader.template.html)的 baseStyles 中有一条无条件规则:

:root:not(.vrtl):not(.vltr),
:root:not(.vrtl):not(.vltr) body {
  font-family: var(--readany-font-family) !important;
}

即使 useBookFonts 处于启用状态(默认开启),这条规则仍把阅读器字体强制应用到 body,覆盖书在 body 上指定的 font-family。因此 epub 嵌入字体在移动端从不生效(很多 epub 在 body 上指定字体)。

桌面端已正确处理(useBookFonts 启用时用 :where(html) 零特异性兜底,书字体优先),移动端未对齐。

修复

对齐桌面端实现(FoliateViewer.tsx):

  • useBookFonts 启用(默认):只注入 :where(html) { font-family: var(--readany-font-family); } —— 零特异性,只影响 htmlbody 继承 html 字体,书在 html/body/任何元素上指定的字体优先
  • useBookFonts 关闭:强制 html, body + 所有后代使用阅读器字体。

reader.htmlbuild:reader 在 prebuild 时自动生成,故本次仅改动模板源码 reader.template.html

验证

  • headless(Chromium)实测:书 body { font-family: "BookEmbeddedFont" } 时,
    • 修复前:"Reader Font", sans-serif(阅读器字体覆盖书)
    • 修复后:BookEmbeddedFont, serif书嵌入字体生效
  • Android 模拟器实测useBookFonts 启用时书字体正常渲染。

说明

  • 这是对已合并的 useBookFonts 功能的移动端 bug 修复,仅改动 1 个文件(reader.template.html)。

变更增补 1:代码字体层叠修复(pre/code/kbd/samp)

body 字体之外,审查中发现等宽规则 pre, code, kbd, samp { ... !important }
在双端都是无条件 !important——同一族 bug:useBookFonts 开启(默认)时,
书自带的代码字体(如嵌入的 @font-face 等宽字体)同样永远无法渲染。

等宽规则现改为随 useBookFonts 开关切换方向:

  • 开启(默认):where(pre, :not(pre) > code, :not(pre) > kbd, :not(pre) > samp)
    零特异性、无 !important——书的任何声明都赢,未声明时等宽链兜底;
  • 关闭(强制覆盖)html body :is(pre, code, kbd, samp) + !important
    !important 不够:特异性在 important 声明之间仍然生效,裸
    pre, code, kbd(0,0,1)会输给书内 body pre { ... !important }(0,0,2);
    (0,0,3) 同时压过两者。

方案对齐 readest#6047 的代码字体加固(其测试思路也被借鉴:在真实引擎中断言
resolved cascade,而非检查选择器文本)。

变更增补 2:代码审查修复(垂直守卫 / 内层继承 / 契约测试)

经 open-code-review 两轮审查后落地三项修正:

  1. 恢复垂直书写守卫:关闭态的强制规则补回
    :root:not(.vrtl):not(.vltr) 前缀(与既有规则一致)。垂直排版的
    文档保持原有行为,不受本 PR 影响。
  2. 修复 <pre><code> 内层继承断裂:UA 样式表自带
    code { font-family: monospace } 直接声明,而任何直接声明都压过继承
    所以书在 pre 上声明的嵌入代码字体传不到内层 code。现为内层元素注入
    零特异性 :where(pre :is(code, kbd, samp)) { font-family: inherit }
    恢复继承链;书内任何针对元素的声明仍然优先于它。独立行内 code 不受影响,
    保持等宽兜底。
  3. 新增契约测试 usebook-fonts-contract.test.ts:将两态规则形状在
    三处副本(模板 / 再生产物 / 桌面生成器)中的一致性钉死,防止这类
    特异性配方在未来被单点修改而静默漂移。

验证增补

  • 无头 Chromium 真实引擎 15 个层叠场景全部通过:两态切换、
    <pre><code> 继承、UA 默认样式拦截、垂直守卫、覆盖与书内
    !important 的特异性决胜(同级与更高级)、书内 code 级声明压过
    inherit 规则;
  • 契约测试 3/3 通过;桌面 tsc --noEmit 干净;
  • 分支已 rebase 到最新 main(feat(reader): 跨平台两端对齐 —— @layer + 最小化 <br> 扫描 #737 两端对齐合并后),无冲突。

The mobile reader.template.html always emitted an unconditional
':root:not(.vrtl):not(.vltr) body { font-family: var(--readany-font-family)
!important }' in baseStyles. Even with useBookFonts enabled (default), this
pinned the reader font on body and overrode the book's own font-family set on
body, so embedded EPUB fonts never rendered on mobile.

Align with the desktop implementation: when useBookFonts is on, emit only a
zero-specificity ':where(html)' fallback so body INHERITS the reader font but
the book's own font-family (on html, body, or any element) wins where
specified. When disabled, force reader font on html/body and every descendant.

reader.html is regenerated by build:reader during prebuild, so only the
template source changes here.

Verified on Android emulator: book fonts now render with useBookFonts enabled.
The pre/code/kbd/samp monospace rule was unconditional !important on
both ends, so with useBookFonts enabled (default) a book's own code
font — e.g. an embedded @font-face on pre — never rendered, the same
class of bug PR codedogQBY#756 fixed for the body font.

The monospace rule now swaps sides with the same toggle:

- useBookFonts on: :where(pre, code, kbd, samp) at zero specificity
  with no !important, so any book declaration wins; without one the
  monospace chain still applies as the fallback.
- useBookFonts off (override): html body :is(pre, code, kbd, samp) with
  !important. !important alone is not enough: specificity still breaks
  ties between important author declarations, so a plain
  'pre, code, kbd' at (0,0,1) loses to an authored
  'body pre { ... !important }' at (0,0,2); (0,0,3) outranks both.

Mirrors readest#6047 (code-font cascade hardening) on top of the
appearance-branch :where approach. Verified the resolved cascade in
headless Chromium across 7 scenarios: book code/body fonts win when
honored, fallbacks still apply, and the override beats authored
!important rules at equal and higher specificity on both ends.
@k6G52m4Dz75W
k6G52m4Dz75W force-pushed the fix/mobile-usebookfonts-clean branch from cf03cf2 to 1e1e521 Compare September 11, 2026 15:56
… contract test

Addresses the 6 findings from the OCR review of the code-font work:

- Restore the horizontal-writing guard (:root:not(.vrtl):not(.vltr)) on
  the forced overrides in the mobile template. The pre-existing rules
  were scoped to horizontal documents and the vertical branch must keep
  its own behavior — the unscoped 'html, body' / 'body *' rules from the
  previous commit leaked into vertical mode (OCR medium).
- Inner code/kbd/samp inside a pre now get a zero-specificity
  'font-family: inherit' instead of just being excluded from the
  monospace fallback: the UA stylesheet's own 'code { font-family:
  monospace }' is a direct declaration, which beats inheritance, so
  exclusion alone still cut '<pre><code>' off from the book's font
  declared on pre (OCR low, verified in a real engine). Any author
  declaration on the element still wins the inherit.
- Add usebook-fonts-contract.test.ts pinning the rule shapes for both
  toggle states across the template, the generated artifact, and the
  desktop generator — the triplicated recipe previously had no
  automated coverage and could drift silently (OCR low x2).

Verified in headless Chromium: 15 scenarios pass covering both toggle
states, inner-code inheritance, UA-default interception, the vertical
guard, and override-vs-authored-important at equal and higher
specificity.
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.

1 participant