fonts: parse font family as well as font style when importing fonts, closes #1876 (#1881)

This commit is contained in:
Huang Xin
2025-08-23 16:27:58 +08:00
committed by GitHub
parent 78cb1f45e4
commit ea9146be5b
4 changed files with 69 additions and 40 deletions
@@ -8,7 +8,7 @@ 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 { parseFontName } from '@/utils/font';
import { getFilename } from '@/utils/path';
import { saveViewSettings } from '../../utils/viewSettingsHelper';
@@ -62,9 +62,11 @@ const CustomFonts: React.FC<CustomFontsProps> = ({ bookKey, onBack }) => {
} else {
continue;
}
const fontFamily = parseFontFamily(await fontFile.arrayBuffer(), fontPath);
const fontName = parseFontName(await fontFile.arrayBuffer(), fontPath);
const customFont = addFont(fontPath, {
name: fontFamily,
name: fontName.name,
family: fontName.family,
style: fontName.style,
});
if (customFont && !customFont.error) {
const loadedFont = await loadFont(envConfig, customFont.id);
@@ -148,7 +150,7 @@ const CustomFonts: React.FC<CustomFontsProps> = ({ bookKey, onBack }) => {
<div className='flex items-center justify-center'>
<MdAdd className='text-primary/85 group-hover:text-primary h-6 w-6' />
</div>
<div className='text-primary/85 group-hover:text-primary font-medium'>
<div className='text-primary/85 group-hover:text-primary line-clamp-1 font-medium'>
{_('Import Font')}
</div>
</div>
@@ -165,6 +167,7 @@ const CustomFonts: React.FC<CustomFontsProps> = ({ bookKey, onBack }) => {
: 'border-base-200 bg-base-200 cursor-pointer',
)}
onClick={() => handleSelectFont(font.id)}
title={font.name}
>
<div className='card-body flex items-center justify-center p-2'>
<div
@@ -174,7 +177,7 @@ const CustomFonts: React.FC<CustomFontsProps> = ({ bookKey, onBack }) => {
}}
className='text-base-content line-clamp-1'
>
{font.name}
{font.family || font.name}
</div>
{isDeleteMode && (
<button
+16 -15
View File
@@ -8,7 +8,10 @@ interface FontStoreState {
loading: boolean;
setFonts: (fonts: CustomFont[]) => void;
addFont: (path: string, options?: { name?: string }) => CustomFont;
addFont: (
path: string,
options?: { name?: string; family?: string; style?: string },
) => CustomFont;
removeFont: (id: string) => boolean;
updateFont: (id: string, updates: Partial<CustomFont>) => boolean;
getFont: (id: string) => CustomFont | undefined;
@@ -45,20 +48,18 @@ export const useCustomFontStore = create<FontStoreState>((set, get) => ({
const font = createCustomFont(path, options);
const existingFont = get().fonts.find((f) => f.id === font.id);
if (existingFont) {
if (existingFont.deletedAt) {
get().updateFont(font.id, {
path: font.path,
downloadedAt: Date.now(),
deletedAt: undefined,
loaded: false,
blobUrl: undefined,
error: undefined,
});
set((state) => ({
fonts: [...state.fonts],
}));
return existingFont;
}
get().updateFont(font.id, {
...font,
path: font.path,
downloadedAt: Date.now(),
deletedAt: undefined,
loaded: false,
blobUrl: undefined,
error: undefined,
});
set((state) => ({
fonts: [...state.fonts],
}));
return existingFont;
}
+5 -1
View File
@@ -118,6 +118,8 @@ export const mountAdditionalFonts = (document: Document, isCJK = false) => {
export interface CustomFont {
id: string;
name: string;
family?: string;
style?: string;
path: string;
downloadedAt?: number;
deletedAt?: number;
@@ -189,10 +191,12 @@ export function createCustomFont(
path: string,
options?: {
name?: string;
family?: string;
style?: string;
},
): CustomFont {
const name = options?.name || getFontName(path);
return { id: getFontId(name), name, path };
return { id: getFontId(name), name, family: options?.family, style: options?.style, path };
}
export const mountCustomFont = (document: Document, font: CustomFont) => {
+40 -19
View File
@@ -75,7 +75,14 @@ function getLanguagePriority(platformID: number, languageID: number, userLanguag
return priority;
}
export const parseFontFamily = (fontData: ArrayBuffer, filename: string) => {
type FontNameType = {
name: string;
platformID: number;
languageID: number;
priority: number;
};
export const parseFontName = (fontData: ArrayBuffer, filename: string) => {
const fallbackName = filename.replace(/\.[^/.]+$/, '');
try {
const dataView = new DataView(fontData);
@@ -108,12 +115,8 @@ export const parseFontFamily = (fontData: ArrayBuffer, filename: string) => {
const stringOffset = dataView.getUint16(nameTableOffset + 4, false);
const userLanguage = getUserLang();
const fontNames: Array<{
name: string;
platformID: number;
languageID: number;
priority: number;
}> = [];
const fontFamilyNames: Array<FontNameType> = [];
const fontStyleNames: Array<FontNameType> = [];
for (let i = 0; i < count; i++) {
const recordOffset = nameTableOffset + 6 + i * 12;
const platformID = dataView.getUint16(recordOffset, false);
@@ -122,36 +125,54 @@ export const parseFontFamily = (fontData: ArrayBuffer, filename: string) => {
const nameLength = dataView.getUint16(recordOffset + 8, false);
const nameOffsetInTable = dataView.getUint16(recordOffset + 10, false);
if (nameID === 1) {
// nameID 1 = Font Family name, nameID 2 = Font Subfamily name (style)
if (nameID === 1 || nameID === 2) {
const stringStart = nameTableOffset + stringOffset + nameOffsetInTable;
let familyName = '';
let fontName = '';
if (platformID === 0 || platformID === 3) {
// Unicode/Microsoft platform
familyName = parseUnicodeString(dataView, stringStart, nameLength);
fontName = parseUnicodeString(dataView, stringStart, nameLength);
} else if (platformID === 1) {
// Macintosh platform
familyName = parseMacintoshString(dataView, stringStart, nameLength);
fontName = parseMacintoshString(dataView, stringStart, nameLength);
}
if (familyName && familyName.trim()) {
if (fontName && fontName.trim()) {
const priority = getLanguagePriority(platformID, languageID, userLanguage);
fontNames.push({
name: familyName.trim(),
const nameEntry = {
name: fontName.trim(),
platformID,
languageID,
priority,
});
};
if (nameID === 1) {
fontFamilyNames.push(nameEntry);
} else if (nameID === 2) {
fontStyleNames.push(nameEntry);
}
}
}
}
if (fontNames.length === 0) {
if (fontFamilyNames.length === 0) {
throw new Error('Font family name not found');
}
fontNames.sort((a, b) => b.priority - a.priority);
return fontNames[0]!.name;
fontFamilyNames.sort((a, b) => b.priority - a.priority);
fontStyleNames.sort((a, b) => b.priority - a.priority);
const familyName = fontFamilyNames[0]!.name;
const styleName = fontStyleNames.length > 0 ? fontStyleNames[0]!.name : '';
return {
name: styleName ? `${familyName} ${styleName}` : familyName,
family: familyName,
style: styleName,
};
} catch (error) {
console.warn(`Failed to parse font: ${error}`);
return fallbackName;
return {
name: fallbackName,
family: fallbackName,
style: '',
};
}
};