feat: apply custom fonts directly in custom font panel (#1870)

This commit is contained in:
Huang Xin
2025-08-22 18:17:57 +08:00
committed by GitHub
parent 45b805dcd4
commit dbee054838
25 changed files with 102 additions and 34 deletions
@@ -532,5 +532,6 @@
"Select Image": "حدد صورة",
"Select Video": "حدد فيديو",
"Select Audio": "حدد صوت",
"Select Fonts": "حدد الخطوط"
"Select Fonts": "حدد الخطوط",
"Supported font formats: .ttf, .odf, .woff, .woff2": "تنسيقات الخط المدعومة: .ttf، .odf، .woff، .woff2"
}
@@ -512,5 +512,6 @@
"Select Image": "རི་མོ་འདེམས་པ།",
"Select Video": "བརྙན་ཟློས་འདེམས་པ།",
"Select Audio": "སྒྲ་ཟློས་འདེམས་པ།",
"Select Fonts": "ཡིག་གཟུགས་འདེམས་པ།"
"Select Fonts": "ཡིག་གཟུགས་འདེམས་པ།",
"Supported font formats: .ttf, .odf, .woff, .woff2": "ཡིག་གཟུགས་འདེམས་པ་བྱས་ནས་བསྐྱར་བརྗེ་བ། ཡིག་གཟུགས་ཀྱི་སྒྲ་སྐད། .ttf, .odf, .woff, .woff2"
}
@@ -516,5 +516,6 @@
"Select Image": "Bild auswählen",
"Select Video": "Video auswählen",
"Select Audio": "Audio auswählen",
"Select Fonts": "Schriftarten auswählen"
"Select Fonts": "Schriftarten auswählen",
"Supported font formats: .ttf, .odf, .woff, .woff2": "Unterstützte Schriftartenformate: .ttf, .odf, .woff, .woff2"
}
@@ -516,5 +516,6 @@
"Select Image": "Επιλογή Εικόνας",
"Select Video": "Επιλογή Βίντεο",
"Select Audio": "Επιλογή Ήχου",
"Select Fonts": "Επιλογή Γραμματοσειρών"
"Select Fonts": "Επιλογή Γραμματοσειρών",
"Supported font formats: .ttf, .odf, .woff, .woff2": "Υποστηριζόμενες μορφές γραμματοσειρών: .ttf, .odf, .woff, .woff2"
}
@@ -520,5 +520,6 @@
"Select Image": "Seleccionar Imagen",
"Select Video": "Seleccionar Video",
"Select Audio": "Seleccionar Audio",
"Select Fonts": "Seleccionar Fuentes"
"Select Fonts": "Seleccionar Fuentes",
"Supported font formats: .ttf, .odf, .woff, .woff2": "Formatos de fuente compatibles: .ttf, .odf, .woff, .woff2"
}
@@ -520,5 +520,6 @@
"Select Image": "Sélectionner une Image",
"Select Video": "Sélectionner une Vidéo",
"Select Audio": "Sélectionner un Audio",
"Select Fonts": "Sélectionner des Polices"
"Select Fonts": "Sélectionner des Polices",
"Supported font formats: .ttf, .odf, .woff, .woff2": "Formats de police pris en charge : .ttf, .odf, .woff, .woff2"
}
@@ -516,5 +516,6 @@
"Select Image": "छवि चुनें",
"Select Video": "वीडियो चुनें",
"Select Audio": "ऑडियो चुनें",
"Select Fonts": "फ़ॉन्ट चुनें"
"Select Fonts": "फ़ॉन्ट चुनें",
"Supported font formats: .ttf, .odf, .woff, .woff2": "समर्थित फ़ॉन्ट प्रारूप: .ttf, .odf, .woff, .woff2"
}
@@ -512,5 +512,6 @@
"Select Image": "Pilih Gambar",
"Select Video": "Pilih Video",
"Select Audio": "Pilih Audio",
"Select Fonts": "Pilih Font"
"Select Fonts": "Pilih Font",
"Supported font formats: .ttf, .odf, .woff, .woff2": "Format font yang didukung: .ttf, .odf, .woff, .woff2"
}
@@ -520,5 +520,6 @@
"Select Image": "Seleziona Immagine",
"Select Video": "Seleziona Video",
"Select Audio": "Seleziona Audio",
"Select Fonts": "Seleziona Font"
"Select Fonts": "Seleziona Font",
"Supported font formats: .ttf, .odf, .woff, .woff2": "Formati di font supportati: .ttf, .odf, .woff, .woff2"
}
@@ -512,5 +512,6 @@
"Select Image": "画像を選択",
"Select Video": "動画を選択",
"Select Audio": "音声を選択",
"Select Fonts": "フォントを選択"
"Select Fonts": "フォントを選択",
"Supported font formats: .ttf, .odf, .woff, .woff2": "サポートされているフォント形式:.ttf, .odf, .woff, .woff2"
}
@@ -512,5 +512,6 @@
"Select Image": "이미지 선택",
"Select Video": "동영상 선택",
"Select Audio": "오디오 선택",
"Select Fonts": "글꼴 선택"
"Select Fonts": "글꼴 선택",
"Supported font formats: .ttf, .odf, .woff, .woff2": "지원되는 글꼴 형식: .ttf, .odf, .woff, .woff2"
}
@@ -516,5 +516,6 @@
"Select Image": "Afbeelding Selecteren",
"Select Video": "Video Selecteren",
"Select Audio": "Audio Selecteren",
"Select Fonts": "Lettertypen Selecteren"
"Select Fonts": "Lettertypen Selecteren",
"Supported font formats: .ttf, .odf, .woff, .woff2": "Ondersteunde lettertypeformaten: .ttf, .odf, .woff, .woff2"
}
@@ -524,5 +524,6 @@
"Select Image": "Wybierz Obraz",
"Select Video": "Wybierz Wideo",
"Select Audio": "Wybierz Audio",
"Select Fonts": "Wybierz Czcionki"
"Select Fonts": "Wybierz Czcionki",
"Supported font formats: .ttf, .odf, .woff, .woff2": "Obsługiwane formaty czcionek: .ttf, .odf, .woff, .woff2"
}
@@ -520,5 +520,6 @@
"Select Image": "Selecionar Imagem",
"Select Video": "Selecionar Vídeo",
"Select Audio": "Selecionar Áudio",
"Select Fonts": "Selecionar Fontes"
"Select Fonts": "Selecionar Fontes",
"Supported font formats: .ttf, .odf, .woff, .woff2": "Formatos de fonte suportados: .ttf, .odf, .woff, .woff2"
}
@@ -524,5 +524,6 @@
"Select Image": "Выбрать Изображение",
"Select Video": "Выбрать Видео",
"Select Audio": "Выбрать Аудио",
"Select Fonts": "Выбрать Шрифты"
"Select Fonts": "Выбрать Шрифты",
"Supported font formats: .ttf, .odf, .woff, .woff2": "Поддерживаемые форматы шрифтов: .ttf, .odf, .woff, .woff2"
}
@@ -512,5 +512,6 @@
"Select Image": "เลือกรูปภาพ",
"Select Video": "เลือกวิดีโอ",
"Select Audio": "เลือกเสียง",
"Select Fonts": "เลือกฟอนต์"
"Select Fonts": "เลือกฟอนต์",
"Supported font formats: .ttf, .odf, .woff, .woff2": "รูปแบบฟอนต์ที่รองรับ: .ttf, .odf, .woff, .woff2"
}
@@ -516,5 +516,6 @@
"Select Image": "Resim Seç",
"Select Video": "Video Seç",
"Select Audio": "Ses Seç",
"Select Fonts": "Yazı Tiplerini Seç"
"Select Fonts": "Yazı Tiplerini Seç",
"Supported font formats: .ttf, .odf, .woff, .woff2": "Desteklenen yazı tipi formatları: .ttf, .odf, .woff, .woff2"
}
@@ -524,5 +524,6 @@
"Select Image": "Вибрати Зображення",
"Select Video": "Вибрати Відео",
"Select Audio": "Вибрати Аудіо",
"Select Fonts": "Вибрати Шрифти"
"Select Fonts": "Вибрати Шрифти",
"Supported font formats: .ttf, .odf, .woff, .woff2": "Підтримувані формати шрифтів: .ttf, .odf, .woff, .woff2"
}
@@ -512,5 +512,6 @@
"Select Image": "Chọn Hình Ảnh",
"Select Video": "Chọn Video",
"Select Audio": "Chọn Âm Thanh",
"Select Fonts": "Chọn Phông Chữ"
"Select Fonts": "Chọn Phông Chữ",
"Supported font formats: .ttf, .odf, .woff, .woff2": "Định dạng phông chữ được hỗ trợ: .ttf, .odf, .woff, .woff2"
}
@@ -512,5 +512,6 @@
"Select Image": "选择图片",
"Select Video": "选择视频",
"Select Audio": "选择音频",
"Select Fonts": "选择字体"
"Select Fonts": "选择字体",
"Supported font formats: .ttf, .odf, .woff, .woff2": "支持的字体格式:.ttf, .odf, .woff, .woff2"
}
@@ -512,5 +512,6 @@
"Select Image": "選擇圖片",
"Select Video": "選擇影片",
"Select Audio": "選擇音訊",
"Select Fonts": "選擇字型"
"Select Fonts": "選擇字型",
"Supported font formats: .ttf, .odf, .woff, .woff2": "支援的字型格式:.ttf, .odf, .woff, .woff2"
}
@@ -176,9 +176,12 @@ const TranslatorPopup: React.FC<TranslatorPopupProps> = ({
className='bg-gray-600 text-white/75'
value={targetLang}
onChange={handleTargetLangChange}
options={Object.entries(translatorLangs)
.sort((a, b) => a[1].localeCompare(b[1]))
.map(([code, name]) => ({ value: code, label: name }))}
options={[
{ value: '', label: _('System Language') },
...Object.entries(translatorLangs)
.sort((a, b) => a[1].localeCompare(b[1]))
.map(([code, name]) => ({ value: code, label: name })),
]}
/>
</div>
{loading ? (
@@ -1,19 +1,23 @@
import clsx from 'clsx';
import React, { useState } from 'react';
import { MdAdd, MdDelete } from 'react-icons/md';
import { IoMdCloseCircleOutline } from 'react-icons/io';
import { useEnv } from '@/context/EnvContext';
import { useReaderStore } from '@/store/readerStore';
import { useTranslation } from '@/hooks/useTranslation';
import { useCustomFontStore } from '@/store/customFontStore';
import { FILE_SELECTION_PRESETS, useFileSelector } from '@/hooks/useFileSelector';
import { mountCustomFont } from '@/styles/fonts';
import { parseFontFamily } from '@/utils/font';
import { getFilename } from '@/utils/path';
import { saveViewSettings } from '../../utils/viewSettingsHelper';
interface CustomFontsProps {
bookKey: string;
onBack: () => void;
}
const CustomFonts: React.FC<CustomFontsProps> = ({ onBack }) => {
const CustomFonts: React.FC<CustomFontsProps> = ({ bookKey, onBack }) => {
const _ = useTranslation();
const { appService, envConfig } = useEnv();
const {
@@ -24,10 +28,18 @@ const CustomFonts: React.FC<CustomFontsProps> = ({ onBack }) => {
getAvailableFonts,
saveCustomFonts,
} = useCustomFontStore();
const { getViewSettings } = useReaderStore();
const viewSettings = getViewSettings(bookKey)!;
const [isDeleteMode, setIsDeleteMode] = useState(false);
const { selectFiles } = useFileSelector(appService, _);
const currentDefaultFont =
viewSettings.defaultFont.toLowerCase() === 'serif' ? 'serif' : 'sans-serif';
const currentFontFamily =
currentDefaultFont === 'serif' ? viewSettings.serifFont : viewSettings.sansSerifFont;
const handleImportFont = () => {
selectFiles({ ...FILE_SELECTION_PRESETS.fonts, multiple: true }).then(async (result) => {
if (result.error || result.files.length === 0) return;
@@ -76,6 +88,17 @@ const CustomFonts: React.FC<CustomFontsProps> = ({ onBack }) => {
}
};
const handleSelectFont = (fontId: string) => {
const font = customFonts.find((f) => f.id === fontId);
if (font) {
if (currentDefaultFont === 'serif') {
saveViewSettings(envConfig, bookKey, 'serifFont', font.name);
} else {
saveViewSettings(envConfig, bookKey, 'sansSerifFont', font.name);
}
}
};
const toggleDeleteMode = () => {
setIsDeleteMode(!isDeleteMode);
};
@@ -87,12 +110,14 @@ const CustomFonts: React.FC<CustomFontsProps> = ({ onBack }) => {
return (
<div className='w-full'>
<div className='mb-6 flex h-8 items-center justify-between'>
<div className='breadcrumbs py-1 text-sm font-medium'>
<div className='breadcrumbs py-1'>
<ul>
<li>
<a onClick={onBack}>{_('Font')}</a>
<a className='font-semibold' onClick={onBack}>
{_('Font')}
</a>
</li>
<li>{_('Custom Fonts')}</li>
<li className='font-medium'>{_('Custom Fonts')}</li>
</ul>
</div>
{availableFonts.length > 0 && (
@@ -131,11 +156,20 @@ const CustomFonts: React.FC<CustomFontsProps> = ({ onBack }) => {
</div>
{availableFonts.map((font) => (
<div key={font.id} className='card border-base-200 bg-base-200 h-12 border shadow-sm'>
<div
key={font.id}
className={clsx(
'card h-12 border shadow-sm',
currentFontFamily === font.name
? 'border-primary/50 bg-primary/50'
: 'border-base-200 bg-base-200 cursor-pointer',
)}
onClick={() => handleSelectFont(font.id)}
>
<div className='card-body flex items-center justify-center p-2'>
<div
style={{
fontFamily: font.loaded ? `"${font.name}", serif` : 'serif',
fontFamily: font.loaded ? `"${font.name}", sans-serif` : 'sans-serif',
fontWeight: 400,
}}
className='text-base-content line-clamp-1 max-w-[90%]'
@@ -159,7 +193,8 @@ const CustomFonts: React.FC<CustomFontsProps> = ({ onBack }) => {
<div className='bg-base-200/30 my-8 rounded-lg p-4'>
<div className='text-base-content/70'>
<div className='mb-1 text-xs font-medium'>{_('Tips')}:</div>
<ul className='list-inside list-disc space-y-1 text-xs'>
<ul className='list-inside list-disc space-y-1 text-sm sm:text-xs'>
<li>{_('Supported font formats: .ttf, .odf, .woff, .woff2')}</li>
<li>{_('Custom fonts can be selected from the Font Face menu')}</li>
</ul>
</div>
@@ -22,6 +22,7 @@ import { useReaderStore } from '@/store/readerStore';
import { useTranslation } from '@/hooks/useTranslation';
import { useSettingsStore } from '@/store/settingsStore';
import { useCustomFontStore } from '@/store/customFontStore';
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
import { getOSPlatform, isCJKEnv } from '@/utils/misc';
import { getSysFontsList } from '@/utils/bridge';
import { isCJKStr } from '@/utils/lang';
@@ -32,7 +33,6 @@ import { SettingsPanelPanelProp } from './SettingsDialog';
import NumberInput from './NumberInput';
import FontDropdown from './FontDropDown';
import CustomFonts from './CustomFonts';
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
const genCJKFontsList = (sysFonts: string[]) => {
return Array.from(new Set([...sysFonts, ...CJK_SERIF_FONTS, ...CJK_SANS_SERIF_FONTS]))
@@ -185,6 +185,12 @@ const FontPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
setCustomFonts(getFontFamilies());
}, [allCustomFonts, getFontFamilies]);
useEffect(() => {
setSerifFont(viewSettings.serifFont);
setSansSerifFont(viewSettings.sansSerifFont);
setMonospaceFont(viewSettings.monospaceFont);
}, [viewSettings.serifFont, viewSettings.sansSerifFont, viewSettings.monospaceFont]);
useEffect(() => {
if (isTauriAppPlatform()) {
getSysFontsList().then((res) => {
@@ -273,7 +279,7 @@ const FontPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
if (fontPanelView === 'custom-fonts') {
return (
<div className='my-4 w-full'>
<CustomFonts onBack={handleBackToMain} />
<CustomFonts bookKey={bookKey} onBack={handleBackToMain} />
</div>
);
}
@@ -40,7 +40,7 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
const [isRtl] = useState(() => getDirFromUILanguage() === 'rtl');
const tabsRef = useRef<HTMLDivElement | null>(null);
const [showAllTabLabels, setShowAllTabLabels] = useState(false);
const { setFontLayoutSettingsDialogOpen } = useSettingsStore();
const { setFontPanelView, setFontLayoutSettingsDialogOpen } = useSettingsStore();
const tabConfig = [
{
@@ -85,6 +85,7 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
const handleSetActivePanel = (tab: SettingsPanelType) => {
setActivePanel(tab);
setFontPanelView('main-fonts');
localStorage.setItem('lastConfigPanel', tab);
};
@@ -115,6 +116,8 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
};
useEffect(() => {
setFontPanelView('main-fonts');
const container = tabsRef.current;
if (!container) return;