1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Desktop: Resolves #9765: OCR: Use existing PDF text when available (#9764)

This commit is contained in:
Henry Heino 2024-02-02 14:59:15 -08:00 committed by GitHub
parent 564adb9a57
commit 815fe8d3ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 45 additions and 3 deletions

View File

@ -82,15 +82,20 @@ describe('OcrService', () => {
// `jest.retryTimes(2)` // `jest.retryTimes(2)`
}, 60000 * 5); }, 60000 * 5);
it('should process PDF resources', async () => { test.each([
const { resource } = await createNoteAndResource({ path: `${ocrSampleDir}/dummy.pdf` }); // Use embedded text (skip OCR)
['dummy.pdf', 'Dummy PDF file'],
['multi_page__embedded_text.pdf', 'This is a test.\nTesting...\nThis PDF has 3 pages.\nThis is page 3.'],
['multi_page__no_embedded_text.pdf', 'This is a multi-page PDF\nwith no embedded text.\nPage 2: more text.\nThe third page.'],
])('should process PDF resources', async (samplePath: string, expectedText: string) => {
const { resource } = await createNoteAndResource({ path: `${ocrSampleDir}/${samplePath}` });
const service = newOcrService(); const service = newOcrService();
await service.processResources(); await service.processResources();
const processedResource: ResourceEntity = await Resource.load(resource.id); const processedResource: ResourceEntity = await Resource.load(resource.id);
expect(processedResource.ocr_text).toBe('Dummy PDF file'); expect(processedResource.ocr_text).toBe(expectedText);
expect(processedResource.ocr_status).toBe(ResourceOcrStatus.Done); expect(processedResource.ocr_status).toBe(ResourceOcrStatus.Done);
expect(processedResource.ocr_error).toBe(''); expect(processedResource.ocr_error).toBe('');

View File

@ -62,6 +62,17 @@ export default class OcrService {
const resourceFilePath = Resource.fullPath(resource); const resourceFilePath = Resource.fullPath(resource);
if (resource.mime === 'application/pdf') { if (resource.mime === 'application/pdf') {
// OCR can be slow for large PDFs.
// Skip it if the PDF already includes text.
const pageTexts = await shim.pdfExtractEmbeddedText(resourceFilePath);
const pagesWithText = pageTexts.filter(text => !!text.trim().length);
if (pagesWithText.length > 0) {
return {
text: pageTexts.join('\n'),
};
}
const imageFilePaths = await shim.pdfToImages(resourceFilePath, await this.pdfExtractDir()); const imageFilePaths = await shim.pdfToImages(resourceFilePath, await this.pdfExtractDir());
const results: RecognizeResult[] = []; const results: RecognizeResult[] = [];

View File

@ -9,6 +9,7 @@ import * as fs from 'fs-extra';
import * as pdfJsNamespace from 'pdfjs-dist'; import * as pdfJsNamespace from 'pdfjs-dist';
import { writeFile } from 'fs/promises'; import { writeFile } from 'fs/promises';
import { ResourceEntity } from './services/database/types'; import { ResourceEntity } from './services/database/types';
import { TextItem } from 'pdfjs-dist/types/src/display/api';
const { FileApiDriverLocal } = require('./file-api-driver-local'); const { FileApiDriverLocal } = require('./file-api-driver-local');
const mimeUtils = require('./mime-utils.js').mime; const mimeUtils = require('./mime-utils.js').mime;
@ -734,6 +735,26 @@ function shimInit(options: ShimInitOptions = null) {
} }
}; };
shim.pdfExtractEmbeddedText = async (pdfPath: string): Promise<string[]> => {
const loadingTask = pdfJs.getDocument(pdfPath);
const doc = await loadingTask.promise;
const textByPage = [];
for (let pageNum = 1; pageNum <= doc.numPages; pageNum++) {
const page = await doc.getPage(pageNum);
const textContent = await page.getTextContent();
const strings = textContent.items.map(item => {
const text = (item as TextItem).str ?? '';
return text;
}).join('\n');
textByPage.push(strings);
}
return textByPage;
};
shim.pdfToImages = async (pdfPath: string, outputDirectoryPath: string): Promise<string[]> => { shim.pdfToImages = async (pdfPath: string, outputDirectoryPath: string): Promise<string[]> => {
// We handle both the Electron app and testing framework. Potentially // We handle both the Electron app and testing framework. Potentially
// the same code could be use to support the CLI app. // the same code could be use to support the CLI app.

View File

@ -277,6 +277,11 @@ const shim = {
throw new Error('Not implemented'); throw new Error('Not implemented');
}, },
// Does not do OCR -- just extracts existing text from a PDF.
pdfExtractEmbeddedText: async (_pdfPath: string): Promise<string[]> => {
throw new Error('Not implemented: textFromPdf');
},
pdfToImages: async (_pdfPath: string, _outputDirectoryPath: string): Promise<string[]> => { pdfToImages: async (_pdfPath: string, _outputDirectoryPath: string): Promise<string[]> => {
throw new Error('Not implemented'); throw new Error('Not implemented');
}, },