feat: add an option to override book fg/bg color, closes #1328 (#1335)

This commit is contained in:
Huang Xin
2025-06-05 00:58:53 +08:00
committed by GitHub
parent 51602a77fb
commit db955d2c8c
23 changed files with 120 additions and 67 deletions
@@ -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": "تجاوز لون الكتاب"
}
@@ -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"
}
@@ -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": "Παράκαμψη χρώματος βιβλίου"
}
@@ -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"
}
@@ -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"
}
@@ -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": "पुस्तक रंग ओवरराइड करें"
}
@@ -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"
}
@@ -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"
}
@@ -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": "書籍の色を上書き"
}
@@ -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": "책 색상 덮어쓰기"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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": "Переопределить цвет книги"
}
@@ -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"
}
@@ -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": "Перевизначити колір книги"
}
@@ -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"
}
@@ -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": "覆盖书籍颜色"
}
@@ -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": "覆蓋書籍顏色"
}
@@ -38,6 +38,7 @@ const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const [editTheme, setEditTheme] = useState<CustomTheme | null>(null);
const [customThems, setCustomThemes] = useState<Theme[]>([]);
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 }) => {
/>
</div>
<div className='flex items-center justify-between'>
<h2 className=''>{_('Override Book Color')}</h2>
<input
type='checkbox'
className='toggle'
checked={overrideColor}
onChange={() => setOverrideColor(!overrideColor)}
/>
</div>
<div>
<h2 className='mb-2 font-medium'>{_('Theme Color')}</h2>
<div className='grid grid-cols-3 gap-4'>
@@ -120,6 +120,7 @@ export const DEFAULT_BOOK_STYLE: BookStyle = {
theme: 'light',
overrideFont: false,
overrideLayout: false,
overrideColor: false,
userStylesheet: '',
};
+1
View File
@@ -105,6 +105,7 @@ export interface BookStyle {
theme: string;
overrideFont: boolean;
overrideLayout: boolean;
overrideColor: boolean;
userStylesheet: string;
}
+61 -46
View File
@@ -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)');