From 891e654e0001b107a87f5d891c20c7c164417d60 Mon Sep 17 00:00:00 2001 From: Retsomm <112182ssss@gmail.com> Date: Fri, 31 Jul 2026 21:41:18 +0800 Subject: [PATCH] =?UTF-8?q?fix(reader):=20=E9=97=9C=E9=96=89=20iOS=20Safar?= =?UTF-8?q?i=20text=20autosizing=20=E9=81=BF=E5=85=8D=20PWA=20=E5=85=A7?= =?UTF-8?q?=E6=96=87=E7=95=B0=E5=B8=B8=E6=94=BE=E5=A4=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iOS Safari(含 standalone PWA)的 text autosizing 機制會依欄寬/裝置寬比例 在渲染時額外放大文字,不反映在 getComputedStyle().fontSize,既有的 inline font-size 覆寫蓋不掉。epub.js 分頁排版是窄欄 CSS columns,特別 容易觸發。注入 text-size-adjust: 100% 關閉此行為。 Co-Authored-By: Claude Sonnet 5 --- pwa-next/src/components/Reader/readerStyles.ts | 11 +++++++++++ pwa-next/src/hooks/reader/useReaderEngine.ts | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pwa-next/src/components/Reader/readerStyles.ts b/pwa-next/src/components/Reader/readerStyles.ts index 34af9c7..0bfc005 100644 --- a/pwa-next/src/components/Reader/readerStyles.ts +++ b/pwa-next/src/components/Reader/readerStyles.ts @@ -90,6 +90,17 @@ export const applyFontSizeOverride = (doc: Document, size: number) => { setTimeout(() => setInlineFontSize(doc, size), 150) } +// iOS Safari(含「加入主畫面」的 standalone PWA)有一個獨立於 font-size 之外的 +// text autosizing 演算法:它會依欄寬/裝置寬的比例,在畫面「渲染時」額外把文字放大 +// 顯示,這個放大不會反映在 getComputedStyle().fontSize 上,所以上面 applyFontSizeOverride +// 逐一覆寫 inline font-size 也蓋不掉它——這正是本機瀏覽器(無此 autosizing 行為)測試正常, +// 只有實機 iOS PWA 才會看到文字被異常放大、擠壓版面的原因。多欄分頁排版(epub.js 的 +// paginated flow 本質上就是窄欄 CSS columns)特別容易觸發這個演算法。 +// 標準解法是明確設定 text-size-adjust: 100%,關閉這個自動放大。 +export const applyTextSizeAdjustOverride = (doc: Document) => { + injectStyle(doc, 'tit-tsa', `html, body, * { -webkit-text-size-adjust: 100% !important; text-size-adjust: 100% !important; }`) +} + // 部分書本(尤其某些直式排版的中文電子書)內建 CSS 會在特定元素(不一定是 html/body, // 可能是內文的 wrapper div)設定 writing-mode: vertical-rl !important,跟本 App 固定的 // 橫向分頁 flow 衝突,疊在一起會變成直排文字塞進橫向分頁容器裡,版面完全跑掉,標點符號 diff --git a/pwa-next/src/hooks/reader/useReaderEngine.ts b/pwa-next/src/hooks/reader/useReaderEngine.ts index 9a0ae94..8fe436c 100644 --- a/pwa-next/src/hooks/reader/useReaderEngine.ts +++ b/pwa-next/src/hooks/reader/useReaderEngine.ts @@ -10,7 +10,7 @@ import type { Annotation } from '@/store/useAnnotationStore' import { saveProgress, loadProgress, saveBookSettings, loadBookSettings } from '@/hooks/useLibrary' import type { BookRecord } from '@/hooks/useLibrary' import { patchBookPrototype, patchIframeViewPrototype, patchRenditionPrototype } from '@/components/Reader/epubPatches' -import { applyDarkOverride, applyFontFamilyOverride, applyFontSizeOverride, applyLetterSpacingOverride, applyLineHeightOverride, applyWritingModeOverride, normalizeFontFamily } from '@/components/Reader/readerStyles' +import { applyDarkOverride, applyFontFamilyOverride, applyFontSizeOverride, applyLetterSpacingOverride, applyLineHeightOverride, applyTextSizeAdjustOverride, applyWritingModeOverride, normalizeFontFamily } from '@/components/Reader/readerStyles' import { convertDoc, getToSC, getToTC, restoreDoc } from '@/components/Reader/scriptConversion' import { DEBUG_TTS_FOLLOW, TTS_HIGHLIGHT_INTERVAL, TTS_NEW_PAGE_AUTO_FOLLOW_GUARD, TTS_PAGE_END_FIXED_LEAD, TTS_USER_INPUT_GRACE, clearTTSHighlight, clearTTSHighlights, collectContentDocuments, createRangeFromTextOffset, ensureTTSHighlightStyle, getBoundaryOffsetFromRange, getTTSRangeViewportState, getTextIndex, paintTTSHighlightOverlay, ttsTextIndexCache } from '@/components/Reader/ttsHighlight' import { computeAccurateTotal, computeChapterAverage, computeGlobalPage, clampProgressRatio, resolveInitialPageInfo } from '@/components/Reader/progressCalculations' @@ -367,6 +367,8 @@ export const useReaderEngine = (params: { // 內文排版方向永遠固定 ltr,理由同上面 forceReadingDirection 的說明——不跟隨 // 使用者的翻頁方向偏好,那個偏好只影響按鈕/手勢要呼叫 next 還是 prev applyWritingModeOverride(doc, 'ltr') + // 關閉 iOS Safari 的 text autosizing,避免實機 PWA 把內文額外放大(見 readerStyles.ts 說明) + applyTextSizeAdjustOverride(doc) // 腳本轉換:只在顯示腳本與書本原始語言不同時才轉換 if (scriptRef.current !== baseScriptRef.current) {