feat: add fallback font loading and handling for various languages

This commit is contained in:
alam00000 committed 2026-09-17 13:56:16 +05:30
1 parent 9ae5c93886
commit 5a7927572f
28 files changed
+375 -4

No files matched your search

+52 -2
View File
@@ -1,7 +1,12 @@
import { describe, expect, it } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { getFontAssetFileName } from '../js/config/font-mappings';
import { resolveFontUrl } from '../js/utils/font-loader';
import {
fallbackFontLanguages,
hasFallbackFonts,
loadFallbackFonts,
resolveFontUrl,
} from '../js/utils/font-loader';
describe('font-loader', () => {
it('uses the default public font URL when no offline font base URL is configured', () => {
@@ -25,4 +30,49 @@ describe('font-loader', () => {
'NotoSansCJKsc-Regular.otf'
);
});
describe('fallback fonts', () => {
afterEach(() => {
vi.unstubAllGlobals();
});
it('needs no font for text the standard fonts can encode', () => {
expect(fallbackFontLanguages('Plain café š € “quotes”').size).toBe(0);
expect([...fallbackFontLanguages('Příliš ŠĐČĆŽ')]).toEqual(['eng']);
});
it('downloads a missing font once and reports success', async () => {
const fetchFont = vi.fn(async () => new Response(new Uint8Array([1])));
vi.stubGlobal('fetch', fetchFont);
const fonts = new Map<string, Uint8Array>();
const results = await Promise.all([
loadFallbackFonts(fonts, ['eng']),
loadFallbackFonts(fonts, ['eng']),
]);
expect(results).toEqual([true, true]);
expect(fetchFont).toHaveBeenCalledTimes(1);
expect(hasFallbackFonts(fonts, ['eng'])).toBe(true);
});
it('reports failure when only a substitute font arrives, then retries', async () => {
vi.stubGlobal(
'fetch',
vi.fn(async (url: string) =>
url.includes('Arabic')
? Promise.reject(new Error('offline'))
: new Response(new Uint8Array([1]))
)
);
const fonts = new Map<string, Uint8Array>();
expect(await loadFallbackFonts(fonts, ['ara'])).toBe(false);
expect(hasFallbackFonts(fonts, ['ara'])).toBe(false);
vi.stubGlobal(
'fetch',
vi.fn(async () => new Response(new Uint8Array([1])))
);
expect(await loadFallbackFonts(fonts, ['ara'])).toBe(true);
expect(hasFallbackFonts(fonts, ['ara'])).toBe(true);
});
});
});
+47 -1
View File
@@ -1,6 +1,9 @@
// @vitest-environment node
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { flattenFreeTextToPageText } from '@/js/utils/freetext-flatten';
import type { FreeTextSystemFontAnnotation } from '@/types';
import {
createEngine,
modelOf,
@@ -113,6 +116,49 @@ describe('flattening FreeText annotations into page text', () => {
expect(target?.lockReason).toBe(0);
});
it('keeps Czech and Croatian letters when no system font is available', async () => {
const font = readFileSync(
resolve(
'node_modules/pdfjs-dist/standard_fonts/LiberationSans-Regular.ttf'
)
);
const fetchFont = vi.fn(async () => new Response(font));
vi.stubGlobal('fetch', fetchFont);
const czech = 'Příliš žluťoučký kůň, úpěl ďábelské ódy.!?';
const croatian = 'ŠĐČĆŽ šđčćž';
const style = (id: string, contents: string) =>
({
id,
pageIndex: 0,
contents,
fontSize: 12,
fontColor: '#000000',
textAlign: 0,
verticalAlign: 0,
opacity: 1,
rect: { origin: { x: 0, y: 0 }, size: { width: 0, height: 0 } },
fontPostScriptName: '',
}) satisfies FreeTextSystemFontAnnotation;
try {
const source = freeTextAnnotationPdf([
{ rect: [72, 600, 540, 640], contents: 'x', name: 'cs' },
{ rect: [72, 500, 540, 540], contents: 'x', name: 'hr' },
]);
const { bytes, flattened } = await flattenFreeTextToPageText(
source,
[style('cs', czech), style('hr', croatian)],
1
);
expect(flattened).toBe(2);
expect(fetchFont).toHaveBeenCalledTimes(1);
const text = allText(modelOf(engine, bytes));
expect(text).toContain(czech);
expect(text).toContain(croatian);
} finally {
vi.unstubAllGlobals();
}
});
it('returns the original bytes when the document cannot be opened', async () => {
const junk = new Uint8Array([1, 2, 3, 4, 5]);
const result = await flattenFreeTextToPageText(junk, [], 1);