From db955d2c8caa670eb7192452d19079b842b0a43b Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Thu, 5 Jun 2025 00:58:53 +0800 Subject: [PATCH] feat: add an option to override book fg/bg color, closes #1328 (#1335) --- .../public/locales/ar/translation.json | 3 +- .../public/locales/de/translation.json | 3 +- .../public/locales/el/translation.json | 3 +- .../public/locales/es/translation.json | 3 +- .../public/locales/fr/translation.json | 3 +- .../public/locales/hi/translation.json | 3 +- .../public/locales/id/translation.json | 7 +- .../public/locales/it/translation.json | 3 +- .../public/locales/ja/translation.json | 3 +- .../public/locales/ko/translation.json | 3 +- .../public/locales/nl/translation.json | 3 +- .../public/locales/pl/translation.json | 3 +- .../public/locales/pt/translation.json | 3 +- .../public/locales/ru/translation.json | 3 +- .../public/locales/tr/translation.json | 3 +- .../public/locales/uk/translation.json | 3 +- .../public/locales/vi/translation.json | 3 +- .../public/locales/zh-CN/translation.json | 3 +- .../public/locales/zh-TW/translation.json | 3 +- .../reader/components/settings/ColorPanel.tsx | 17 +++ apps/readest-app/src/services/constants.ts | 1 + apps/readest-app/src/types/book.ts | 1 + apps/readest-app/src/utils/style.ts | 107 ++++++++++-------- 23 files changed, 120 insertions(+), 67 deletions(-) diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 4821d010..ca6b878e 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -352,5 +352,6 @@ "Never synced": "لم تُزامَن", "Show Remaining Time": "إظهار الوقت المتبقي", "Show Page Number": "إظهار رقم الصفحة", - "{{time}} min left in chapter": "{{time}} دقيقة متبقية في الفصل" + "{{time}} min left in chapter": "{{time}} دقيقة متبقية في الفصل", + "Override Book Color": "تجاوز لون الكتاب" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 7c764ed3..01271b40 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -344,5 +344,6 @@ "Never synced": "Nie synchronisiert", "Show Remaining Time": "Verbleibende Zeit anzeigen", "Show Page Number": "Seitenzahl anzeigen", - "{{time}} min left in chapter": "{{time}} min verbleibend im Kapitel" + "{{time}} min left in chapter": "{{time}} min verbleibend im Kapitel", + "Override Book Color": "Farbe des Buches überschreiben" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index 9e19101a..f8db907b 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -344,5 +344,6 @@ "Never synced": "Ποτέ δεν συγχρονίστηκε", "Show Remaining Time": "Εμφάνιση υπολειπόμενου χρόνου", "Show Page Number": "Εμφάνιση αριθμού σελίδας", - "{{time}} min left in chapter": "{{time}} λεπτά απομένουν στο κεφάλαιο" + "{{time}} min left in chapter": "{{time}} λεπτά απομένουν στο κεφάλαιο", + "Override Book Color": "Παράκαμψη χρώματος βιβλίου" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 67f418b9..6547e934 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -346,5 +346,6 @@ "Never synced": "Nunca sincronizado", "Show Remaining Time": "Mostrar tiempo restante", "Show Page Number": "Mostrar número de página", - "{{time}} min left in chapter": "{{time}} min restantes en el capítulo" + "{{time}} min left in chapter": "{{time}} min restantes en el capítulo", + "Override Book Color": "Sobre escribir color del libro" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index 9b800928..e46df438 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -346,5 +346,6 @@ "Never synced": "Jamais synchronisé", "Show Remaining Time": "Afficher le temps restant", "Show Page Number": "Afficher le numéro de page", - "{{time}} min left in chapter": "{{time}} min restant dans le chapitre" + "{{time}} min left in chapter": "{{time}} min restant dans le chapitre", + "Override Book Color": "Remplacer la couleur du livre" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index 1fe40264..f58ba21b 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -344,5 +344,6 @@ "Never synced": "कभी सिंक नहीं किया गया", "Show Remaining Time": "शेष समय दिखाएं", "Show Page Number": "पृष्ठ संख्या दिखाएं", - "{{time}} min left in chapter": "{{time}} मिनट शेष अध्याय में" + "{{time}} min left in chapter": "{{time}} मिनट शेष अध्याय में", + "Override Book Color": "पुस्तक रंग ओवरराइड करें" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 7a72f484..071763c7 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -340,7 +340,8 @@ "Sign in to Sync": "Masuk untuk Sinkronisasi", "Synced at {{time}}": "Disinkronkan pada {{time}}", "Never synced": "Belum pernah disinkronkan", - "Show Remaining Time": "__STRING_NOT_TRANSLATED__", - "Show Page Number": "__STRING_NOT_TRANSLATED__", - "{{time}} min left in chapter": "{{time}} menit tersisa di bab" + "Show Remaining Time": "Perlihatkan Waktu Tersisa", + "Show Page Number": "Perlihatkan Nomor Halaman", + "{{time}} min left in chapter": "{{time}} menit tersisa di bab", + "Override Book Color": "Ubah Warna Buku" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index cf499844..397c11e2 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -346,5 +346,6 @@ "Never synced": "Mai sincronizzato", "Show Remaining Time": "Mostra tempo rimanente", "Show Page Number": "Mostra numero pagina", - "{{time}} min left in chapter": "{{time}} min rimasti nel capitolo" + "{{time}} min left in chapter": "{{time}} min rimasti nel capitolo", + "Override Book Color": "Override colore libro" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index 17160f04..d6e38520 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -342,5 +342,6 @@ "Never synced": "まだ同期されていません", "Show Remaining Time": "残り時間を表示", "Show Page Number": "ページ番号を表示", - "{{time}} min left in chapter": "{{time}}分残り" + "{{time}} min left in chapter": "{{time}}分残り", + "Override Book Color": "書籍の色を上書き" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index d77a5be5..3fc44a09 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -342,5 +342,6 @@ "Never synced": "동기화된 적 없음", "Show Remaining Time": "남은 시간 표시", "Show Page Number": "페이지 번호 표시", - "{{time}} min left in chapter": "{{time}}분 남음" + "{{time}} min left in chapter": "{{time}}분 남음", + "Override Book Color": "책 색상 덮어쓰기" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index f294584c..9ecd1585 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -344,5 +344,6 @@ "Never synced": "Nooit gesynchroniseerd", "Show Remaining Time": "Toon resterende tijd", "Show Page Number": "Toon paginanummer", - "{{time}} min left in chapter": "Nog {{time}} min in hoofdstuk" + "{{time}} min left in chapter": "Nog {{time}} min in hoofdstuk", + "Override Book Color": "Boekkleur overschrijven" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 6c0d7d15..cb9cbbfb 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -348,5 +348,6 @@ "Never synced": "Brak synchronizacji", "Show Remaining Time": "Pokazuj pozostały czas", "Show Page Number": "Pokaż numer strony", - "{{time}} min left in chapter": "{{time}} min pozostało do końca rozdziału" + "{{time}} min left in chapter": "{{time}} min pozostało do końca rozdziału", + "Override Book Color": "Nadpisz kolor książki" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index ab4719f9..ad926ee6 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -346,5 +346,6 @@ "Never synced": "Nunca sincronizado", "Show Remaining Time": "Mostrar Tempo Restante", "Show Page Number": "Mostrar Número da Página", - "{{time}} min left in chapter": "{{time}} min restantes no capítulo" + "{{time}} min left in chapter": "{{time}} min restantes no capítulo", + "Override Book Color": "Substituir Cor do Livro" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index fcde56d1..286251f3 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -348,5 +348,6 @@ "Never synced": "Не синхр.", "Show Remaining Time": "Показать оставшееся время", "Show Page Number": "Показать номер страницы", - "{{time}} min left in chapter": "{{time}} мин осталось в главе" + "{{time}} min left in chapter": "{{time}} мин осталось в главе", + "Override Book Color": "Переопределить цвет книги" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 52d1a3c4..ae1cb5eb 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -344,5 +344,6 @@ "Never synced": "Hiç eşzlenmedi", "Show Remaining Time": "Kalan Süreyi Göster", "Show Page Number": "Sayfa Numarasını Göster", - "{{time}} min left in chapter": "{{time}} dakika bölümde kaldı" + "{{time}} min left in chapter": "{{time}} dakika bölümde kaldı", + "Override Book Color": "Kitap Rengini Geçersiz Kıl" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 2df90487..982bb958 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -348,5 +348,6 @@ "Never synced": "Ніколи не синхр.", "Show Remaining Time": "Показати залишок часу", "Show Page Number": "Показати номер сторінки", - "{{time}} min left in chapter": "{{time}} хв до кінця розділу" + "{{time}} min left in chapter": "{{time}} хв до кінця розділу", + "Override Book Color": "Перевизначити колір книги" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index 15aec8c2..bec62326 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -342,5 +342,6 @@ "Never synced": "Chưa từng đồng bộ", "Show Remaining Time": "Hiển thị thời gian còn lại", "Show Page Number": "Hiển thị số trang", - "{{time}} min left in chapter": "{{time}} phút còn lại trong chương" + "{{time}} min left in chapter": "{{time}} phút còn lại trong chương", + "Override Book Color": "Đổi màu sách" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index e6039467..2f6ea9c8 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -342,5 +342,6 @@ "Never synced": "从未同步", "Show Remaining Time": "显示剩余时间", "Show Page Number": "显示页码", - "{{time}} min left in chapter": "本章剩余 {{time}} 分钟" + "{{time}} min left in chapter": "本章剩余 {{time}} 分钟", + "Override Book Color": "覆盖书籍颜色" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index a98ce032..1094ff75 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -342,5 +342,6 @@ "Never synced": "從未同步", "Show Remaining Time": "顯示剩餘時間", "Show Page Number": "顯示頁碼", - "{{time}} min left in chapter": "本章剩餘 {{time}} 分鐘" + "{{time}} min left in chapter": "本章剩餘 {{time}} 分鐘", + "Override Book Color": "覆蓋書籍顏色" } diff --git a/apps/readest-app/src/app/reader/components/settings/ColorPanel.tsx b/apps/readest-app/src/app/reader/components/settings/ColorPanel.tsx index 9e78aa39..c444696f 100644 --- a/apps/readest-app/src/app/reader/components/settings/ColorPanel.tsx +++ b/apps/readest-app/src/app/reader/components/settings/ColorPanel.tsx @@ -38,6 +38,7 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { const [editTheme, setEditTheme] = useState(null); const [customThems, setCustomThemes] = useState([]); const [showCustomThemeEditor, setShowCustomThemeEditor] = useState(false); + const [overrideColor, setOverrideColor] = useState(viewSettings.overrideColor!); useEffect(() => { if (invertImgColorInDark === viewSettings.invertImgColorInDark) return; @@ -45,6 +46,12 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [invertImgColorInDark]); + useEffect(() => { + if (overrideColor === viewSettings.overrideColor) return; + saveViewSettings(envConfig, bookKey, 'overrideColor', overrideColor); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [overrideColor]); + useEffect(() => { const customThemes = settings.globalReadSettings.customThemes ?? []; setCustomThemes( @@ -137,6 +144,16 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { /> +
+

{_('Override Book Color')}

+ setOverrideColor(!overrideColor)} + /> +
+

{_('Theme Color')}

diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index 75175ea6..115b3126 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -120,6 +120,7 @@ export const DEFAULT_BOOK_STYLE: BookStyle = { theme: 'light', overrideFont: false, overrideLayout: false, + overrideColor: false, userStylesheet: '', }; diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts index 34a17421..24db530d 100644 --- a/apps/readest-app/src/types/book.ts +++ b/apps/readest-app/src/types/book.ts @@ -105,6 +105,7 @@ export interface BookStyle { theme: string; overrideFont: boolean; overrideLayout: boolean; + overrideColor: boolean; userStylesheet: string; } diff --git a/apps/readest-app/src/utils/style.ts b/apps/readest-app/src/utils/style.ts index 63e88f83..88807255 100644 --- a/apps/readest-app/src/utils/style.ts +++ b/apps/readest-app/src/utils/style.ts @@ -26,9 +26,7 @@ const getFontStyles = ( minFontSize: number, fontWeight: number, overrideFont: boolean, - themeCode: ThemeCode, ) => { - const { fg, primary, isDarkMode } = themeCode; const lastSerifFonts = ['Georgia', 'Times New Roman']; const serifFonts = [ serif, @@ -82,13 +80,54 @@ const getFontStyles = ( font[size="7"] { font-size: ${fontSize * 3}px; } + /* hardcoded inline font size */ + [style*="font-size: 16px"], [style*="font-size:16px"] { + font-size: 1rem !important; + } body * { ${overrideFont ? 'font-family: revert !important;' : ''} } + + `; + return fontStyles; +}; + +const getColorStyles = ( + overrideColor: boolean, + invertImgColorInDark: boolean, + themeCode: ThemeCode, +) => { + const { bg, fg, primary, isDarkMode } = themeCode; + const colorStyles = ` + html { + --theme-bg-color: ${bg}; + --theme-fg-color: ${fg}; + --theme-primary-color: ${primary}; + color-scheme: ${isDarkMode ? 'dark' : 'light'}; + } + html, body { + color: ${fg}; + } + div, p, span, pre { + ${overrideColor ? `background-color: ${bg} !important;` : ''} + } a:any-link { - ${overrideFont ? `color: ${primary};` : isDarkMode ? `color: lightblue;` : ''} + ${overrideColor ? `color: ${primary};` : isDarkMode ? `color: lightblue;` : ''} text-decoration: none; } + p:has(img), span:has(img) { + background-color: ${bg}; + } + body.pbg { + ${isDarkMode ? `background-color: ${bg} !important;` : ''} + } + img { + ${isDarkMode && invertImgColorInDark ? 'filter: invert(100%);' : ''} + } + /* inline images */ + p img, span img, sup img { + mix-blend-mode: ${isDarkMode ? 'screen' : 'multiply'}; + } /* override inline hardcoded text color */ *[style*="color: rgb(0,0,0)"], *[style*="color: rgb(0, 0, 0)"], *[style*="color: #000"], *[style*="color: #000000"], *[style*="color: black"], @@ -96,8 +135,20 @@ const getFontStyles = ( *[style*="color:#000"], *[style*="color:#000000"], *[style*="color:black"] { color: ${fg} !important; } + /* for the Gutenberg eBooks */ + #pg-header * { + color: inherit !important; + } + .x-ebookmaker, .x-ebookmaker-cover, .x-ebookmaker-coverpage { + background-color: unset !important; + } + /* for the Feedbooks eBooks */ + .chapterHeader, .chapterHeader * { + border-color: unset; + background-color: ${bg} !important; + } `; - return fontStyles; + return colorStyles; }; const getLayoutStyles = ( @@ -112,19 +163,10 @@ const getLayoutStyles = ( zoomLevel: number, writingMode: string, vertical: boolean, - invertImgColorInDark: boolean, - themeCode: ThemeCode, ) => { - const { bg, fg, primary, isDarkMode } = themeCode; const layoutStyle = ` @namespace epub "http://www.idpf.org/2007/ops"; html { - color-scheme: ${isDarkMode ? 'dark' : 'light'}; - } - html { - --theme-bg-color: ${bg}; - --theme-fg-color: ${fg}; - --theme-primary-color: ${primary}; --default-text-align: ${justify ? 'justify' : 'start'}; hanging-punctuation: allow-end last; orphans: 2; @@ -146,7 +188,6 @@ const getLayoutStyles = ( --background-set: var(--theme-bg-color); } html, body { - color: ${fg}; ${writingMode === 'auto' ? '' : `writing-mode: ${writingMode} !important;`} text-align: var(--default-text-align); max-height: unset; @@ -207,9 +248,6 @@ const getLayoutStyles = ( } /* Now begins really dirty hacks to fix some badly designed epubs */ - body.pbg { - ${isDarkMode ? `background-color: ${bg} !important;` : ''} - } img.pi { ${vertical ? 'transform: rotate(90deg);' : ''} ${vertical ? 'transform-origin: center;' : ''} @@ -227,25 +265,13 @@ const getLayoutStyles = ( color: unset; } - img { - ${isDarkMode && invertImgColorInDark ? 'filter: invert(100%);' : ''} - } /* inline images without dimension */ p img, span img, sup img { height: 1em; - mix-blend-mode: ${isDarkMode ? 'screen' : 'multiply'}; } p:has(> img:only-child) img, span:has(> img:only-child) img { height: auto; } - p:has(img), span:has(img) { - background-color: ${bg}; - } - - /* hardcoded inline font size */ - [style*="font-size: 16px"], [style*="font-size:16px"] { - font-size: 1rem !important; - } /* workaround for some badly designed epubs */ div.left *, p.left * { text-align: left; } @@ -256,20 +282,6 @@ const getLayoutStyles = ( .nonindent, .noindent { text-indent: unset !important; } - - /* for the Gutenberg eBooks */ - #pg-header * { - color: inherit !important; - } - .x-ebookmaker, .x-ebookmaker-cover, .x-ebookmaker-coverpage { - background-color: unset !important; - } - - /* for the Feedbooks eBooks */ - .chapterHeader, .chapterHeader * { - border-color: unset; - background-color: ${bg} !important; - } `; return layoutStyle; }; @@ -370,8 +382,6 @@ export const getStyles = (viewSettings: ViewSettings, themeCode?: ThemeCode) => viewSettings.zoomLevel! / 100.0, viewSettings.writingMode!, viewSettings.vertical!, - viewSettings.invertImgColorInDark!, - themeCode, ); // scale the font size on-the-fly so that we can sync the same font size on different devices const isMobile = ['ios', 'android'].includes(getOSPlatform()); @@ -386,11 +396,15 @@ export const getStyles = (viewSettings: ViewSettings, themeCode?: ThemeCode) => viewSettings.minimumFontSize!, viewSettings.fontWeight!, viewSettings.overrideFont!, + ); + const colorStyles = getColorStyles( + viewSettings.overrideColor!, + viewSettings.invertImgColorInDark!, themeCode, ); const translationStyles = getTranslationStyles(); const userStylesheet = viewSettings.userStylesheet!; - return `${layoutStyles}\n${fontStyles}\n${translationStyles}\n${userStylesheet}`; + return `${layoutStyles}\n${fontStyles}\n${colorStyles}\n${translationStyles}\n${userStylesheet}`; }; export const transformStylesheet = ( @@ -442,6 +456,7 @@ export const transformStylesheet = ( }) .replace(/(\d*\.?\d+)vw/gi, (_, d) => (parseFloat(d) * w) / 100 + 'px') .replace(/(\d*\.?\d+)vh/gi, (_, d) => (parseFloat(d) * h) / 100 + 'px') + .replace(/[\s;]color\s*:\s*black/gi, 'color: var(--theme-fg-color)') .replace(/[\s;]color\s*:\s*#000000/gi, 'color: var(--theme-fg-color)') .replace(/[\s;]color\s*:\s*#000/gi, 'color: var(--theme-fg-color)') .replace(/[\s;]color\s*:\s*rgb\(0,\s*0,\s*0\)/gi, 'color: var(--theme-fg-color)');