fix(ocr): Arabic+Latin font fix and added tests

This commit is contained in:
alam00000 committed 2026-05-19 15:05:17 +05:30
1 parent 58a0053778
commit 0ce043c18e
8 files changed
+356 -165

No files matched your search

+24 -8
View File
@@ -32,14 +32,26 @@ describe('compare OCR page recognition', () => {
mockWorker.recognize.mockResolvedValue({
data: {
words: [
blocks: [
{
text: 'Hello',
bbox: { x0: 10, y0: 20, x1: 60, y1: 40 },
},
{
text: 'world',
bbox: { x0: 70, y0: 20, x1: 120, y1: 40 },
paragraphs: [
{
lines: [
{
words: [
{
text: 'Hello',
bbox: { x0: 10, y0: 20, x1: 60, y1: 40 },
},
{
text: 'world',
bbox: { x0: 70, y0: 20, x1: 120, y1: 40 },
},
],
},
],
},
],
},
],
},
@@ -52,7 +64,11 @@ describe('compare OCR page recognition', () => {
1,
expect.any(Function)
);
expect(mockWorker.recognize).toHaveBeenCalledWith(canvas);
expect(mockWorker.recognize).toHaveBeenCalledWith(
canvas,
{},
{ text: true, blocks: true }
);
expect(mockWorker.terminate).toHaveBeenCalledTimes(1);
expect(model.source).toBe('ocr');
expect(model.hasText).toBe(true);
+2 -2
View File
@@ -12,11 +12,11 @@ describe('font-loader', () => {
it('builds a self-hosted font URL when an OCR font base URL is configured', () => {
expect(
resolveFontUrl('Noto Sans Arabic', {
resolveFontUrl('Noto Naskh Arabic', {
VITE_OCR_FONT_BASE_URL: 'https://internal.example.com/wasm/ocr/fonts/',
})
).toBe(
'https://internal.example.com/wasm/ocr/fonts/NotoSansArabic-Regular.ttf'
'https://internal.example.com/wasm/ocr/fonts/NotoNaskhArabic-Regular.ttf'
);
});
+27
View File
@@ -8,6 +8,7 @@ import {
normalizeText,
calculateWordTransform,
calculateSpaceTransform,
calculateLineFontSize,
} from '../js/utils/hocr-transform';
import type { OcrWord } from '@/types';
@@ -298,6 +299,32 @@ describe('hocr-transform', () => {
});
});
describe('calculateLineFontSize', () => {
it('returns line height + baseline intercept', () => {
const line = {
bbox: { x0: 0, y0: 100, x1: 500, y1: 130 },
baseline: { slope: 0, intercept: 2 },
textangle: 0,
words: [] as OcrWord[],
direction: 'ltr' as const,
injectWordBreaks: true,
};
expect(calculateLineFontSize(line)).toBe(32);
});
it('clamps to a minimum of 1 for degenerate lines', () => {
const line = {
bbox: { x0: 0, y0: 0, x1: 0, y1: 0 },
baseline: { slope: 0, intercept: 0 },
textangle: 0,
words: [] as OcrWord[],
direction: 'ltr' as const,
injectWordBreaks: true,
};
expect(calculateLineFontSize(line)).toBe(1);
});
});
describe('calculateSpaceTransform', () => {
const baseLine = {
bbox: { x0: 0, y0: 100, x1: 500, y1: 130 },
+16 -11
View File
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const {
createConfiguredTesseractWorker,
@@ -23,6 +23,7 @@ const mockWorker = {
const mockPdfPage = {
getViewport: vi.fn(() => ({ width: 200, height: 100 })),
render: vi.fn(() => ({ promise: Promise.resolve() })),
cleanup: vi.fn(),
};
const mockPdfOutputPage = {
@@ -177,27 +178,31 @@ describe('performOcr', () => {
expect.any(Function)
);
expect(mockWorker.setParameters).toHaveBeenCalledWith({
tessjs_create_hocr: '1',
tessedit_pageseg_mode: '3',
});
expect(mockWorker.recognize).toHaveBeenCalledTimes(1);
expect(mockWorker.terminate).toHaveBeenCalledTimes(1);
expect(result.fullText).toContain('Recognized text');
expect(result.pdfBytes).toBeInstanceOf(Uint8Array);
expect(result.warnings).toEqual([]);
});
it('terminates the Tesseract worker when OCR fails', async () => {
it('records a warning and still terminates the worker when OCR fails on a page', async () => {
mockWorker.recognize.mockRejectedValueOnce(new Error('ocr failed'));
await expect(
performOcr(new Uint8Array([1, 2, 3]), {
language: 'eng',
resolution: 2,
binarize: false,
whitelist: '',
})
).rejects.toThrow('ocr failed');
const result = await performOcr(new Uint8Array([1, 2, 3]), {
language: 'eng',
resolution: 2,
binarize: false,
whitelist: '',
});
expect(mockWorker.terminate).toHaveBeenCalledTimes(1);
expect(result.warnings).toHaveLength(1);
expect(result.warnings[0]).toMatchObject({
page: 1,
kind: 'recognize-error',
});
expect(result.warnings[0].message).toContain('ocr failed');
});
});