diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx index 896fd1af..5a04210c 100644 --- a/apps/readest-app/src/app/library/page.tsx +++ b/apps/readest-app/src/app/library/page.tsx @@ -32,7 +32,7 @@ import { useThemeStore } from '@/store/themeStore'; import { useScreenWakeLock } from '@/hooks/useScreenWakeLock'; import { useOpenWithBooks } from '@/hooks/useOpenWithBooks'; import { lockScreenOrientation } from '@/utils/bridge'; -import { mountAdditionalFonts } from '@/utils/style'; +import { mountAdditionalFonts } from '@/utils/font'; import { tauriHandleSetAlwaysOnTop, tauriHandleToggleFullScreen, diff --git a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx index 31269053..a0cff8f5 100644 --- a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx +++ b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx @@ -5,13 +5,15 @@ import { FoliateView, wrappedFoliateView } from '@/types/view'; import { useEnv } from '@/context/EnvContext'; import { useThemeStore } from '@/store/themeStore'; import { useReaderStore } from '@/store/readerStore'; +import { useBookDataStore } from '@/store/bookDataStore'; import { useParallelViewStore } from '@/store/parallelViewStore'; import { useMouseEvent, useTouchEvent } from '../hooks/useIframeEvents'; import { usePagination } from '../hooks/usePagination'; import { useFoliateEvents } from '../hooks/useFoliateEvents'; import { useProgressSync } from '../hooks/useProgressSync'; import { useProgressAutoSave } from '../hooks/useProgressAutoSave'; -import { getStyles, mountAdditionalFonts, transformStylesheet } from '@/utils/style'; +import { getStyles, transformStylesheet } from '@/utils/style'; +import { mountAdditionalFonts } from '@/utils/font'; import { getBookDirFromLanguage, getBookDirFromWritingMode } from '@/utils/book'; import { useUICSS } from '@/hooks/useUICSS'; import { @@ -26,6 +28,7 @@ import { } from '../utils/iframeEventHandlers'; import { getMaxInlineSize } from '@/utils/config'; import { getDirFromUILanguage } from '@/utils/rtl'; +import { isCJKLang } from '@/utils/lang'; import { transformContent } from '@/services/transformService'; import { lockScreenOrientation } from '@/utils/bridge'; import { useTextTranslation } from '../hooks/useTextTranslation'; @@ -42,6 +45,7 @@ const FoliateViewer: React.FC<{ const { getView, setView: setFoliateView, setProgress } = useReaderStore(); const { getViewSettings, setViewSettings } = useReaderStore(); const { getParallels } = useParallelViewStore(); + const { getBookData } = useBookDataStore(); const { themeCode, isDarkMode } = useThemeStore(); const [toastMessage, setToastMessage] = useState(''); @@ -92,6 +96,7 @@ const FoliateViewer: React.FC<{ if (detail.doc) { const writingDir = viewRef.current?.renderer.setStyles && getDirection(detail.doc); const viewSettings = getViewSettings(bookKey)!; + const bookData = getBookData(bookKey)!; viewSettings.vertical = writingDir?.vertical || viewSettings.writingMode.includes('vertical') || false; viewSettings.rtl = @@ -101,7 +106,7 @@ const FoliateViewer: React.FC<{ false; setViewSettings(bookKey, { ...viewSettings }); - mountAdditionalFonts(detail.doc); + mountAdditionalFonts(detail.doc, isCJKLang(bookData.book?.primaryLanguage)); if (!detail.doc.isEventListenersAdded) { // listened events in iframes are posted to the main window diff --git a/apps/readest-app/src/app/reader/components/FootnotePopup.tsx b/apps/readest-app/src/app/reader/components/FootnotePopup.tsx index 0e65150a..91355288 100644 --- a/apps/readest-app/src/app/reader/components/FootnotePopup.tsx +++ b/apps/readest-app/src/app/reader/components/FootnotePopup.tsx @@ -2,12 +2,15 @@ import React, { useEffect, useRef, useState } from 'react'; import { BookDoc } from '@/libs/document'; import { useReaderStore } from '@/store/readerStore'; +import { useBookDataStore } from '@/store/bookDataStore'; import { useFoliateEvents } from '../hooks/useFoliateEvents'; -import { getFootnoteStyles, getStyles, getThemeCode, mountAdditionalFonts } from '@/utils/style'; +import { getFootnoteStyles, getStyles, getThemeCode } from '@/utils/style'; import { getPopupPosition, getPosition, Position } from '@/utils/sel'; +import { FootnoteHandler } from 'foliate-js/footnotes.js'; +import { mountAdditionalFonts } from '@/utils/font'; import { eventDispatcher } from '@/utils/event'; import { FoliateView } from '@/types/view'; -import { FootnoteHandler } from 'foliate-js/footnotes.js'; +import { isCJKLang } from '@/utils/lang'; import Popup from '@/components/Popup'; interface FootnotePopupProps { @@ -26,6 +29,7 @@ const FootnotePopup: React.FC = ({ bookKey, bookDoc }) => { const [popupPosition, setPopupPosition] = useState(); const [showPopup, setShowPopup] = useState(false); + const { getBookData } = useBookDataStore(); const { getView, getViewSettings } = useReaderStore(); const view = getView(bookKey); const viewSettings = getViewSettings(bookKey)!; @@ -55,7 +59,8 @@ const FootnotePopup: React.FC = ({ bookKey, bookDoc }) => { }); view.addEventListener('load', (e: CustomEvent) => { const { doc } = e.detail; - mountAdditionalFonts(doc); + const bookData = getBookData(bookKey)!; + mountAdditionalFonts(doc, isCJKLang(bookData.book?.primaryLanguage)); }); footnoteViewRef.current = view; footnoteRef.current?.replaceChildren(view); diff --git a/apps/readest-app/src/app/reader/components/Reader.tsx b/apps/readest-app/src/app/reader/components/Reader.tsx index ed6a79cd..754f1fae 100644 --- a/apps/readest-app/src/app/reader/components/Reader.tsx +++ b/apps/readest-app/src/app/reader/components/Reader.tsx @@ -15,8 +15,8 @@ import { useSettingsStore } from '@/store/settingsStore'; import { useDeviceControlStore } from '@/store/deviceStore'; import { useScreenWakeLock } from '@/hooks/useScreenWakeLock'; import { eventDispatcher } from '@/utils/event'; -import { interceptGlobalOpen } from '@/utils/open'; -import { mountAdditionalFonts } from '@/utils/style'; +import { interceptWindowOpen } from '@/utils/open'; +import { mountAdditionalFonts } from '@/utils/font'; import { setSystemUIVisibility } from '@/utils/bridge'; import { AboutWindow } from '@/components/AboutWindow'; import { UpdaterWindow } from '@/components/UpdaterWindow'; @@ -40,7 +40,7 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => { useEffect(() => { mountAdditionalFonts(document); - interceptGlobalOpen(); + interceptWindowOpen(); }, []); useEffect(() => { diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index 11bca9de..c40fee0a 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -189,7 +189,7 @@ export const CJK_SERIF_FONTS = [ export const CJK_SANS_SERIF_FONTS = ['Noto Sans SC', 'Noto Sans TC']; -export const SANS_SERIF_FONTS = ['Roboto', 'Noto Sans', 'Open Sans', 'Helvetica', 'Arial']; +export const SANS_SERIF_FONTS = ['Roboto', 'Noto Sans', 'Open Sans', 'Helvetica']; export const MONOSPACE_FONTS = ['Fira Code', 'Lucida Console', 'Consolas', 'Courier New']; diff --git a/apps/readest-app/src/utils/font.ts b/apps/readest-app/src/utils/font.ts new file mode 100644 index 00000000..1366dcd5 --- /dev/null +++ b/apps/readest-app/src/utils/font.ts @@ -0,0 +1,109 @@ +import { isCJKEnv } from './misc'; + +const basicGoogleFonts = [ + { family: 'Bitter', weights: 'ital,wght@0,100..900;1,100..900' }, + { family: 'Fira Code', weights: 'wght@300..700' }, + { family: 'Literata', weights: 'ital,opsz,wght@0,7..72,200..900;1,7..72,200..900' }, + { family: 'Merriweather', weights: 'ital,opsz,wght@0,18..144,300..900;1,18..144,300..900' }, + { family: 'Noto Sans', weights: 'ital,wght@0,100..900;1,100..900' }, + { family: 'Open Sans', weights: 'ital,wght@0,300..800;1,300..800' }, + { family: 'Roboto', weights: 'ital,wght@0,100..900;1,100..900' }, + { family: 'Vollkorn', weights: 'ital,wght@0,400..900;1,400..900' }, +]; + +const cjkGoogleFonts = [ + { family: 'LXGW WenKai TC', weights: '' }, + { family: 'Noto Sans SC', weights: '' }, + { family: 'Noto Sans TC', weights: '' }, + { family: 'Noto Serif JP', weights: '' }, +]; + +const getAdditionalBasicFontLinks = () => ` + +`; + +const getAdditionalCJKFontLinks = () => ` + + + + +`; + +const getAdditionalCJKFontFaces = () => ` + @font-face { + font-family: "FangSong"; + font-display: swap; + src: local("Fang Song"), local("FangSong"), local("Noto Serif CJK"), local("Source Han Serif SC VF"), url("https://db.onlinewebfonts.com/t/2ecbfe1d9bfc191c6f15c0ccc23cbd43.eot"); + src: url("https://db.onlinewebfonts.com/t/2ecbfe1d9bfc191c6f15c0ccc23cbd43.eot?#iefix") format("embedded-opentype"), + url("https://db.onlinewebfonts.com/t/2ecbfe1d9bfc191c6f15c0ccc23cbd43.woff2") format("woff2"), + url("https://db.onlinewebfonts.com/t/2ecbfe1d9bfc191c6f15c0ccc23cbd43.woff") format("woff"), + url("https://db.onlinewebfonts.com/t/2ecbfe1d9bfc191c6f15c0ccc23cbd43.ttf") format("truetype"), + url("https://db.onlinewebfonts.com/t/2ecbfe1d9bfc191c6f15c0ccc23cbd43.svg#FangSong") format("svg"); + } + @font-face { + font-family: "Kaiti"; + font-display: swap; + src: local("Kai"), local("KaiTi"), local("AR PL UKai"), local("LXGW WenKai GB Screen"), url("https://db.onlinewebfonts.com/t/1ee9941f1b8c128110ca4307dda59917.eot"); + src: url("https://db.onlinewebfonts.com/t/1ee9941f1b8c128110ca4307dda59917.eot?#iefix")format("embedded-opentype"), + url("https://db.onlinewebfonts.com/t/1ee9941f1b8c128110ca4307dda59917.woff2")format("woff2"), + url("https://db.onlinewebfonts.com/t/1ee9941f1b8c128110ca4307dda59917.woff")format("woff"), + url("https://db.onlinewebfonts.com/t/1ee9941f1b8c128110ca4307dda59917.ttf")format("truetype"), + url("https://db.onlinewebfonts.com/t/1ee9941f1b8c128110ca4307dda59917.svg#STKaiti")format("svg"); + } + @font-face { + font-family: "Heiti"; + font-display: swap; + src: local("Hei"), local("SimHei"), local("WenQuanYi Zen Hei"), local("Source Han Sans SC VF"), url("https://db.onlinewebfonts.com/t/a4948b9d43a91468825a5251df1ec58d.eot"); + src: url("https://db.onlinewebfonts.com/t/a4948b9d43a91468825a5251df1ec58d.eot?#iefix")format("embedded-opentype"), + url("https://db.onlinewebfonts.com/t/a4948b9d43a91468825a5251df1ec58d.woff2")format("woff2"), + url("https://db.onlinewebfonts.com/t/a4948b9d43a91468825a5251df1ec58d.woff")format("woff"), + url("https://db.onlinewebfonts.com/t/a4948b9d43a91468825a5251df1ec58d.ttf")format("truetype"), + url("https://db.onlinewebfonts.com/t/a4948b9d43a91468825a5251df1ec58d.svg#WenQuanYi Micro Hei")format("svg"); + } + @font-face { + font-family: "XiHeiti"; + font-display: swap; + src: local("PingFang SC"), local("Microsoft YaHei"), local("WenQuanYi Micro Hei"), local("FZHei-B01"), url("https://db.onlinewebfonts.com/t/4f0b783ba4a1b381fc7e7af81ecab481.eot"); + src: url("https://db.onlinewebfonts.com/t/4f0b783ba4a1b381fc7e7af81ecab481.eot?#iefix")format("embedded-opentype"), + url("https://db.onlinewebfonts.com/t/4f0b783ba4a1b381fc7e7af81ecab481.woff2")format("woff2"), + url("https://db.onlinewebfonts.com/t/4f0b783ba4a1b381fc7e7af81ecab481.woff")format("woff"), + url("https://db.onlinewebfonts.com/t/4f0b783ba4a1b381fc7e7af81ecab481.ttf")format("truetype"), + url("https://db.onlinewebfonts.com/t/4f0b783ba4a1b381fc7e7af81ecab481.svg#STHeiti J Light")format("svg"); +} +`; + +export const mountAdditionalFonts = (document: Document, isCJK = false) => { + const mountCJKFonts = isCJK || isCJKEnv(); + let links = getAdditionalBasicFontLinks(); + if (mountCJKFonts) { + const style = document.createElement('style'); + style.textContent = getAdditionalCJKFontFaces(); + document.head.appendChild(style); + + links = `${links}\n${getAdditionalCJKFontLinks()}`; + } + + const parser = new DOMParser(); + const parsedDocument = parser.parseFromString(links, 'text/html'); + + Array.from(parsedDocument.head.children).forEach((child) => { + if (child.tagName === 'LINK') { + const link = document.createElement('link'); + link.rel = child.getAttribute('rel') || ''; + link.href = child.getAttribute('href') || ''; + link.crossOrigin = child.getAttribute('crossorigin') || ''; + + document.head.appendChild(link); + } + }); +}; diff --git a/apps/readest-app/src/utils/lang.ts b/apps/readest-app/src/utils/lang.ts index 64794305..dfcf3df8 100644 --- a/apps/readest-app/src/utils/lang.ts +++ b/apps/readest-app/src/utils/lang.ts @@ -2,6 +2,12 @@ export const isCJKStr = (str: string) => { return /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u.test(str ?? ''); }; +export const isCJKLang = (lang: string | null | undefined): boolean => { + if (!lang) return false; + const normalizedLang = lang.split('-')[0]!.toLowerCase(); + return ['zh', 'ja', 'ko'].includes(normalizedLang); +}; + export const langToDefaultLocale = (langCode: string): string => { const mapping: Record = { en: 'en-US', diff --git a/apps/readest-app/src/utils/open.ts b/apps/readest-app/src/utils/open.ts index 1fc2186a..c8165ef5 100644 --- a/apps/readest-app/src/utils/open.ts +++ b/apps/readest-app/src/utils/open.ts @@ -1,7 +1,7 @@ import { openUrl } from '@tauri-apps/plugin-opener'; import { isTauriAppPlatform } from '@/services/environment'; -export const interceptGlobalOpen = () => { +export const interceptWindowOpen = () => { const windowOpen = window.open; globalThis.open = function ( url?: string | URL, diff --git a/apps/readest-app/src/utils/style.ts b/apps/readest-app/src/utils/style.ts index 2d169e95..63e88f83 100644 --- a/apps/readest-app/src/utils/style.ts +++ b/apps/readest-app/src/utils/style.ts @@ -14,7 +14,6 @@ import { generateLightPalette, generateDarkPalette, } from '@/styles/themes'; - import { getOSPlatform } from './misc'; const getFontStyles = ( @@ -101,75 +100,6 @@ const getFontStyles = ( return fontStyles; }; -const googleFontsData = [ - { family: 'Bitter', weights: 'ital,wght@0,100..900;1,100..900' }, - { family: 'Fira Code', weights: 'wght@300..700' }, - { family: 'Literata', weights: 'ital,opsz,wght@0,7..72,200..900;1,7..72,200..900' }, - { family: 'Merriweather', weights: 'ital,opsz,wght@0,18..144,300..900;1,18..144,300..900' }, - { family: 'Noto Sans', weights: 'ital,wght@0,100..900;1,100..900' }, - { family: 'Roboto', weights: 'ital,wght@0,100..900;1,100..900' }, - { family: 'Vollkorn', weights: 'ital,wght@0,400..900;1,400..900' }, - { family: 'LXGW WenKai TC' }, - { family: 'Noto Sans SC' }, - { family: 'Noto Sans TC' }, - { family: 'Noto Serif JP' }, -]; - -const getAdditionalFontLinks = () => ` - - - - -`; - -const getAdditionalFontFaces = () => ` - @font-face { - font-family: "FangSong"; - font-display: swap; - src: local("Fang Song"), local("FangSong"), local("Noto Serif CJK"), local("Source Han Serif SC VF"), url("https://db.onlinewebfonts.com/t/2ecbfe1d9bfc191c6f15c0ccc23cbd43.eot"); - src: url("https://db.onlinewebfonts.com/t/2ecbfe1d9bfc191c6f15c0ccc23cbd43.eot?#iefix") format("embedded-opentype"), - url("https://db.onlinewebfonts.com/t/2ecbfe1d9bfc191c6f15c0ccc23cbd43.woff2") format("woff2"), - url("https://db.onlinewebfonts.com/t/2ecbfe1d9bfc191c6f15c0ccc23cbd43.woff") format("woff"), - url("https://db.onlinewebfonts.com/t/2ecbfe1d9bfc191c6f15c0ccc23cbd43.ttf") format("truetype"), - url("https://db.onlinewebfonts.com/t/2ecbfe1d9bfc191c6f15c0ccc23cbd43.svg#FangSong") format("svg"); - } - @font-face { - font-family: "Kaiti"; - font-display: swap; - src: local("Kai"), local("KaiTi"), local("AR PL UKai"), local("LXGW WenKai GB Screen"), url("https://db.onlinewebfonts.com/t/1ee9941f1b8c128110ca4307dda59917.eot"); - src: url("https://db.onlinewebfonts.com/t/1ee9941f1b8c128110ca4307dda59917.eot?#iefix")format("embedded-opentype"), - url("https://db.onlinewebfonts.com/t/1ee9941f1b8c128110ca4307dda59917.woff2")format("woff2"), - url("https://db.onlinewebfonts.com/t/1ee9941f1b8c128110ca4307dda59917.woff")format("woff"), - url("https://db.onlinewebfonts.com/t/1ee9941f1b8c128110ca4307dda59917.ttf")format("truetype"), - url("https://db.onlinewebfonts.com/t/1ee9941f1b8c128110ca4307dda59917.svg#STKaiti")format("svg"); - } - @font-face { - font-family: "Heiti"; - font-display: swap; - src: local("Hei"), local("SimHei"), local("WenQuanYi Zen Hei"), local("Source Han Sans SC VF"), url("https://db.onlinewebfonts.com/t/a4948b9d43a91468825a5251df1ec58d.eot"); - src: url("https://db.onlinewebfonts.com/t/a4948b9d43a91468825a5251df1ec58d.eot?#iefix")format("embedded-opentype"), - url("https://db.onlinewebfonts.com/t/a4948b9d43a91468825a5251df1ec58d.woff2")format("woff2"), - url("https://db.onlinewebfonts.com/t/a4948b9d43a91468825a5251df1ec58d.woff")format("woff"), - url("https://db.onlinewebfonts.com/t/a4948b9d43a91468825a5251df1ec58d.ttf")format("truetype"), - url("https://db.onlinewebfonts.com/t/a4948b9d43a91468825a5251df1ec58d.svg#WenQuanYi Micro Hei")format("svg"); - } - @font-face { - font-family: "XiHeiti"; - font-display: swap; - src: local("PingFang SC"), local("Microsoft YaHei"), local("WenQuanYi Micro Hei"), local("FZHei-B01"), url("https://db.onlinewebfonts.com/t/4f0b783ba4a1b381fc7e7af81ecab481.eot"); - src: url("https://db.onlinewebfonts.com/t/4f0b783ba4a1b381fc7e7af81ecab481.eot?#iefix")format("embedded-opentype"), - url("https://db.onlinewebfonts.com/t/4f0b783ba4a1b381fc7e7af81ecab481.woff2")format("woff2"), - url("https://db.onlinewebfonts.com/t/4f0b783ba4a1b381fc7e7af81ecab481.woff")format("woff"), - url("https://db.onlinewebfonts.com/t/4f0b783ba4a1b381fc7e7af81ecab481.ttf")format("truetype"), - url("https://db.onlinewebfonts.com/t/4f0b783ba4a1b381fc7e7af81ecab481.svg#STHeiti J Light")format("svg"); -} -`; - const getLayoutStyles = ( overrideLayout: boolean, paragraphMargin: number, @@ -463,28 +393,6 @@ export const getStyles = (viewSettings: ViewSettings, themeCode?: ThemeCode) => return `${layoutStyles}\n${fontStyles}\n${translationStyles}\n${userStylesheet}`; }; -export const mountAdditionalFonts = (document: Document) => { - const links = getAdditionalFontLinks(); - - const parser = new DOMParser(); - const parsedDocument = parser.parseFromString(links, 'text/html'); - - Array.from(parsedDocument.head.children).forEach((child) => { - if (child.tagName === 'LINK') { - const link = document.createElement('link'); - link.rel = child.getAttribute('rel') || ''; - link.href = child.getAttribute('href') || ''; - link.crossOrigin = child.getAttribute('crossorigin') || ''; - - document.head.appendChild(link); - } - }); - - const style = document.createElement('style'); - style.textContent = getAdditionalFontFaces(); - document.head.appendChild(style); -}; - export const transformStylesheet = ( viewSettings: ViewSettings, width: number,