Skip to content

OCR: Add Docling engine - #16231

Merged
InAnYan merged 21 commits into
JabRef:mainfrom
ZiadAbdElFatah:docling-engine
Aug 1, 2026
Merged

OCR: Add Docling engine#16231
InAnYan merged 21 commits into
JabRef:mainfrom
ZiadAbdElFatah:docling-engine

Conversation

@ZiadAbdElFatah

@ZiadAbdElFatah ZiadAbdElFatah commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Related issues and pull requests

Closes #13267

PR Description

Add Docling as the second engine for OCR

Steps to test

AI usage

Claude (Sonnet 4.6 through web), used for discussion and to double-check the solution.

Checklist

  • I own the copyright of the code submitted and I license it under the MIT license
  • If AI tools were used, I disclosed them in the "AI usage" section and reviewed, understood, and take full ownership of all AI-generated code
  • I manually tested my changes in running JabRef (always required)
  • [/] I added JUnit tests for changes (if applicable)
  • [/] I added screenshots in the PR description (if change is visible to the user)
  • [/] I added a screenshot in the PR description showing a library with a single entry with me as author and as title the issue number
  • [/] I described the change in CHANGELOG.md in a way that can be understood by the average user (if change is visible to the user)
  • [/] I checked the user documentation for up to dateness and submitted a pull request to our user documentation repository

@github-actions github-actions Bot added the status: changes-required Pull requests that are not yet complete label Jul 13, 2026
@subhramit subhramit changed the title Docling engine OCR: Add Docling engine Jul 13, 2026
Comment thread jablib/src/main/java/module-info.java Outdated
exports org.jabref.logic.ai.summarization.util;
exports org.jabref.logic.msc;
exports org.jabref.logic.ai.models;
exports org.jabref.logic.ocr.Docling;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be lowercase

@InAnYan InAnYan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! This is a good start and I think you are in right direction!

@github-actions

Copy link
Copy Markdown
Contributor

The requested changes were not addressed for 3 days. Please follow-up in the next 7 days or your PR will be automatically closed. You can check the contributing guidelines for hints on the pull request process.

@github-actions github-actions Bot added the status: stale Issues marked by a bot as "stale". All issues need to be investigated manually. label Jul 25, 2026
@github-actions github-actions Bot removed the status: stale Issues marked by a bot as "stale". All issues need to be investigated manually. label Jul 28, 2026
@ZiadAbdElFatah
ZiadAbdElFatah marked this pull request as ready for review July 29, 2026 12:38
@ZiadAbdElFatah
ZiadAbdElFatah requested a review from InAnYan July 29, 2026 12:38
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

OCR: Add Docling engine and shared OCR utilities

✨ Enhancement ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add a Docling-based OCR engine that extracts text spans and embeds them into PDFs.
• Refactor shared OCR timeouts/output naming into a reusable utility.
• Wire GUI OCR action to use Docling and export the new module package.
Diagram

graph TD
  GUI["OcrLinkedFileAction"] --> ENG["DoclingEngine"] --> CLI{{"docling CLI"}}
  CLI --> JSON[("Docling JSON")]
  JSON --> PDFBOX["PDFBox embed"] --> OUT[("Searchable PDF")]
  ENG -. "uses" .-> UTILS["OcrUtils"]
  subgraph Legend
    direction LR
    _code["Code"] ~~~ _ext{{"External"}} ~~~ _file[("File")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Make OCR engine selection configurable (Strategy/Preference)
  • ➕ Avoids hard-coding Docling in the GUI action
  • ➕ Enables choosing between OCRmyPDF and Docling per user/platform
  • ➕ Reduces future churn when adding more engines
  • ➖ Requires UI/preferences plumbing and migration for existing settings
  • ➖ Slightly more upfront complexity than a direct switch
2. Use a temp directory / streaming to avoid writing JSON next to the PDF
  • ➕ Keeps user folders clean and avoids permission issues in read-only directories
  • ➕ Reduces risk of collisions with existing *.json files
  • ➖ Requires temp lifecycle management and error handling
  • ➖ Harder to debug without persisted intermediate output
3. Delegate availability/path handling to a shared external-process helper
  • ➕ Consistent isAvailable/timeout behavior across engines
  • ➕ Allows using preferences for the Docling binary path like OCRmyPDF
  • ➖ Needs additional abstraction and refactoring across engines

Recommendation: The overall approach (Docling CLI -> JSON -> PDFBox embed) is reasonable for producing searchable PDFs, and the shared OcrUtils extraction is a good start. The biggest improvement to consider next is making engine selection (and engine binary path/availability checks) configurable rather than hard-coded in the GUI action, so Docling is truly a “second engine” instead of a replacement. Also consider running Docling in a temp location to avoid leaving intermediate artifacts in user directories (even though this PR deletes the JSON after embedding).

Files changed (9) +239 / -23

Enhancement (6) +211 / -2
OcrLinkedFileAction.javaSwitch GUI OCR action to use DoclingEngine +3/-2

Switch GUI OCR action to use DoclingEngine

• Replaces the OCR engine instantiated by the linked-file OCR action with DoclingEngine (leaving the prior OCRmyPDF engine commented out). This changes which backend is used when users run OCR from the GUI.

jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java

DoclingBBox.javaAdd Docling bounding-box DTO +4/-0

Add Docling bounding-box DTO

• Adds a record representing a text span bounding box (left/top/right/bottom) from Docling JSON output.

jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingBBox.java

DoclingDocument.javaAdd Docling document DTO for JSON root +6/-0

Add Docling document DTO for JSON root

• Adds a record matching the Docling JSON root structure containing a list of extracted text entries.

jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingDocument.java

DoclingEngine.javaImplement Docling OCR engine with JSON parsing and PDFBox embedding +186/-0

Implement Docling OCR engine with JSON parsing and PDFBox embedding

• Adds a new OcrEngine implementation that runs the external docling CLI to generate JSON output, groups extracted text spans by page, and appends invisible text via PDFBox at the extracted coordinates. Deletes the intermediate JSON after producing the searchable PDF and filters out characters not encodable by the chosen PDF font.

jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java

DoclingProv.javaAdd Docling provenance DTO (page + bbox) +6/-0

Add Docling provenance DTO (page + bbox)

• Adds a record for Docling provenance metadata, including page number mapping via @JsonProperty("page_no") and the associated bounding box.

jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingProv.java

DoclingText.javaAdd Docling text-span DTO +6/-0

Add Docling text-span DTO

• Adds a record representing a single extracted text span and its provenance list as emitted by Docling JSON.

jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingText.java

Refactor (2) +27 / -21
OcrMyPdfEngine.javaRefactor OCRmyPDF engine to use shared OCR utilities +4/-21

Refactor OCRmyPDF engine to use shared OCR utilities

• Moves shared constants (timeouts, output naming) and output-path generation out of this class and into OcrUtils. Updates the engine to reference OcrUtils for timeouts and output filename generation.

jablib/src/main/java/org/jabref/logic/ocr/OcrMyPdfEngine.java

OcrUtils.javaAdd shared OCR constants and output-path helper +23/-0

Add shared OCR constants and output-path helper

• Introduces a small utility class that centralizes OCR timeouts and the logic for deriving the output “_ocr.pdf” path from an input PDF path.

jablib/src/main/java/org/jabref/logic/ocr/OcrUtils.java

Other (1) +1 / -0
module-info.javaExport Docling OCR package +1/-0

Export Docling OCR package

• Exports org.jabref.logic.ocr.docling so the new engine and DTOs are accessible to other modules.

jablib/src/main/java/module-info.java

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Unchecked prov/page index 🐞 Bug ☼ Reliability
Description
DoclingEngine.embedText() assumes each DoclingText has a non-empty prov list and that the derived
0-based page index is valid for the PDF, so unexpected Docling output can throw runtime exceptions
instead of returning an OcrResult.Failure. These exceptions bypass the normal failure-reason UI and
end up in the generic onFailure handler.
Code

jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[R129-133]

+        for (DoclingText doclingText : doclingDocument.texts()) {
+            // Docling outputs the pages number 1 indexed, while PDFBox uses 0 indexed pages
+            int pageNo = doclingText.prov().getFirst().pageNo() - 1;
+            pageTextMap.computeIfAbsent(pageNo, _ -> new ArrayList<>()).add(doclingText);
+        }
Evidence
The code calls getFirst() on the prov list with no null/emptiness checks, derives a 0-based page
index, and then indexes the PDF pages directly; the GUI’s onFailure handler is used for uncaught
exceptions, not for OcrResult.Failure reasons.

jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[124-133]
jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[140-143]
jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingText.java[5-6]
jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java[98-101]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`embedText()` uses `doclingText.prov().getFirst()` and then blindly calls `pdfWithText.getPage(pageNo)`. If `prov` is null/empty or `pageNo` is out of bounds (including negative after subtracting 1), OCR can fail via unhandled runtime exceptions.
## Issue Context
The GUI has a dedicated failure-reason mapping for `OcrResult.Failure`, but unexpected exceptions route through the background task failure handler instead.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[124-133]
- jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[140-143]
- jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java[79-101]
## Suggested fix
- Guard `doclingText.prov()` for null/empty before calling `getFirst()`; skip invalid entries (and log) or convert to an `OcrResult.Failure`.
- Validate `pageNo` with `0 <= pageNo < pdfWithText.getNumberOfPages()` before calling `getPage`.
- If no valid text blocks remain after filtering, return a deterministic `OcrResult.Failure` (choose an appropriate reason) rather than succeeding with an unchanged PDF.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Hardcoded DoclingEngine selection ✓ Resolved 📎 Requirement gap ⚙ Maintainability
Description
OcrLinkedFileAction hardcodes instantiation of DoclingEngine, so the app cannot actually support
multiple OCR backends behind the OcrEngine abstraction and existing OCR preferences (which default
to OCRmyPDF) are effectively ignored. This reduces extensibility, conflicts with the multi-backend
OCR requirement, and can regress OCR for users who configured OCRmyPDF or don’t have Docling
installed while still showing OCRmyPDF-specific NOT_AVAILABLE messaging.
Code

jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java[R59-60]

+        //        this.ocrEngine = new OcrMyPdfEngine(preferences.getOcrPreferences());
+        this.ocrEngine = new DoclingEngine(preferences.getOcrPreferences());
Evidence
PR Compliance ID 2 requires a common interface that enables multiple OCR engines to be supported,
but in OcrLinkedFileAction the constructor assigns ocrEngine directly to `new
DoclingEngine(...)` (with the previous engine usage commented out), demonstrating there is no engine
selection or plugging mechanism at the invocation point. In addition, OCR preferences still default
to OCRmyPDF and the NOT_AVAILABLE failure UI text is explicitly OCRmyPDF-specific, which together
shows the current behavior bypasses user configuration while presenting engine-specific messaging
inconsistent with the hardcoded Docling backend.

Implement a common OCR engine interface to support multiple OCR backends
jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java[59-60]
jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java[59-61]
jablib/src/main/java/org/jabref/logic/ocr/OcrPreferences.java[12-18]
jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java[105-117]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`OcrLinkedFileAction` unconditionally instantiates `DoclingEngine`, which prevents true multi-backend support behind the `OcrEngine` interface, bypasses existing OCR preferences (defaulting to OCRmyPDF), and leaves user-visible failure messages hardcoded to OCRmyPDF.
## Issue Context
Compliance requires a common OCR engine interface that can actually support multiple backends; hardcoding the engine selection inside GUI action code blocks extensibility and can break configured setups (e.g., users expecting OCRmyPDF or environments without Docling). Currently, `OcrPreferences` defaults the engine path to `ocrmypdf`, yet the action always uses Docling and still shows NOT_AVAILABLE messaging that explicitly says “OCRmyPDF is not available…”, which should instead be engine-appropriate.
## Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java[59-61]
- jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java[105-117]
- jablib/src/main/java/org/jabref/logic/ocr/OcrPreferences.java[12-18]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. DoclingEngine ignores expert config ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
DoclingEngine hardcodes the executable name (docling) and isAvailable() always returns true,
so expert configuration via OcrPreferences is effectively bypassed and availability cannot be
validated. This prevents correct NOT_AVAILABLE handling and can turn missing/undiscoverable
Docling into confusing runtime IO_ERROR failures while blocking users from configuring the engine
path/settings.
Code

jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[R46-88]

+    @Override
+    public boolean isAvailable() {
+        return true;
+        //        ArrayList<String> command = StringUtil.splitRespectingEscapedWhitespace(ocrPreferences.getOcrEnginePath());
+        //        command.add("--version");
+        //        try {
+        //            ProcessBuilder processBuilder = new ProcessBuilder(command);
+        //            processBuilder.redirectErrorStream(true);
+        //            Process process = processBuilder.start();
+        //            boolean finished = process.waitFor(OcrUtils.CHECKING_TIMEOUT, TimeUnit.SECONDS);
+        //            if (!finished) {
+        //                process.destroyForcibly();
+        //                LOGGER.debug("Checking Docling availability timed out");
+        //                return false;
+        //            }
+        //            return process.exitValue() == 0;
+        //        } catch (IOException e) {
+        //            LOGGER.error("Docling is not available at {}: IOException occurred", ocrPreferences.getOcrEnginePath(), e);
+        //            return false;
+        //        } catch (InterruptedException e) {
+        //            Thread.currentThread().interrupt();
+        //            LOGGER.error("Checking Docling availability was interrupted", e);
+        //            return false;
+        //        }
+    }
+
+    @Override
+    public OcrResult performOcrAndEmbedText(Path pdfPath) {
+        if (!isAvailable()) {
+            return OcrResult.failure(OcrFailureReason.NOT_AVAILABLE);
+        }
+        Path outputDir = pdfPath.getParent();
+        // although a list of Strings, it represents a single command as that is how the ProcessBuilder expects it.
+        ArrayList<String> command = new ArrayList<>();
+        command.add("docling");
+        command.add("--to");
+        command.add("json");
+        command.add("--no-tables");
+        command.add("--image-export-mode");
+        command.add("placeholder");
+        command.add("--output");
+        command.add(outputDir.toString());
+        command.add(pdfPath.toString());
Evidence
The cited implementation adds the literal "docling" to the ProcessBuilder command
(command.add("docling")), while the preference-based executable path is only present in commented
code, showing the configured ocrEnginePath is ignored during execution. Separately,
isAvailable() explicitly returns true with the intended availability check commented out, which
violates the requirement for both good defaults and an expert configuration pathway (PR Compliance
ID 4) and makes it impossible to validate presence or return OcrFailureReason.NOT_AVAILABLE before
attempting execution.

Offer good default OCR settings and allow expert configuration of OCR settings
jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[46-70]
jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[77-88]
jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[46-50]
jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[72-88]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`DoclingEngine` currently (1) always reports available via `isAvailable()` and (2) hardcodes the `docling` command instead of using `OcrPreferences` (e.g., `getOcrEnginePath()`) for expert configuration, which prevents proper availability validation and correct `NOT_AVAILABLE` handling.
## Issue Context
The OCR feature should ship with usable defaults but still allow expert configuration (such as a custom executable path). With `isAvailable()` returning `true` unconditionally and the ProcessBuilder command always starting with the literal `docling` (while the preference-based path is commented out), configuration cannot work and missing/undiscoverable Docling is likely to surface as a runtime `IO_ERROR` rather than a clean `NOT_AVAILABLE` failure.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[46-121]
## Suggested fix
- Build the command from `ocrPreferences.getOcrEnginePath()` (similar to `OcrMyPdfEngine` using `StringUtil.splitRespectingEscapedWhitespace`).
- Implement `isAvailable()` to run a lightweight probe with a timeout (and return false on IOException/timeout/interrupt).
- Ensure the failure reason mapping distinguishes NOT_AVAILABLE from IO_ERROR (start failure).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Missing @NullMarked on new classes ⊘ Outdated 📘 Rule violation ☼ Reliability
Description
The newly added Docling OCR types are not annotated with @NullMarked, leaving nullability
contracts implicit. This increases the risk of null-related bugs and makes APIs harder to reason
about.
Code

jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[R1-39]

+package org.jabref.logic.ocr.docling;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.jabref.logic.ocr.OcrEngine;
+import org.jabref.logic.ocr.OcrFailureReason;
+import org.jabref.logic.ocr.OcrPreferences;
+import org.jabref.logic.ocr.OcrResult;
+import org.jabref.logic.ocr.OcrUtils;
+import org.jabref.logic.util.HeadlessExecutorService;
+import org.jabref.logic.util.StreamGobbler;
+
+import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
+import org.apache.pdfbox.pdmodel.graphics.state.RenderingMode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import tools.jackson.databind.json.JsonMapper;
+
+/// Implementation of the {@link OcrEngine} interface using Docling.
+public class DoclingEngine implements OcrEngine {
+
+    public static final Logger LOGGER = LoggerFactory.getLogger(DoclingEngine.class);
+    private final OcrPreferences ocrPreferences;
+
+    public DoclingEngine(OcrPreferences ocrPreferences) {
+        this.ocrPreferences = ocrPreferences;
+    }
Evidence
PR Compliance ID 17 requires new classes to enforce non-null defaults with @NullMarked. The new
Docling OCR classes/records are introduced without @NullMarked annotations.

AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked: AGENTS.md: Prefer Optional and explicit nullability via JSpecify; enforce non-null defaults with @NullMarked
jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[1-39]
jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingText.java[1-6]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New classes/records in `org.jabref.logic.ocr.docling` are missing `@NullMarked`, so nullability defaults are not enforced/communicated.
## Issue Context
The project prefers explicit nullability via JSpecify with non-null defaults.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[1-39]
- jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingText.java[1-6]
- jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingProv.java[1-6]
- jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingDocument.java[1-6]
- jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingBBox.java[1-4]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
5. Cleanup masks success ⊘ Outdated 🐞 Bug ≡ Correctness
Description
embedText() saves the output PDF and then unconditionally deletes the JSON output via Files.delete,
so a deletion failure (missing file, filesystem restrictions, etc.) turns a successful OCR output
into an IO_ERROR result. This can report OCR as failed even though the OCRed PDF was produced.
Code

jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[R166-170]

+            pdfWithText.save(outputPdf.toFile());
+        }
+
+        Files.delete(jsonOutputPath);
+        return OcrResult.success(outputPdf);
Evidence
The code saves the PDF, then deletes the JSON; since embedText throws IOException, any deletion
failure is caught by performOcrAndEmbedText’s IOException handler and mapped to IO_ERROR.

jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[166-170]
jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[113-121]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
After `pdfWithText.save(...)`, `Files.delete(jsonOutputPath)` can throw, causing `performOcrAndEmbedText` to return `OcrFailureReason.IO_ERROR` even when the OCRed PDF has already been written.
## Issue Context
Cleanup should not determine overall success; it should be best-effort.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[166-170]
- jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[113-121]
## Suggested fix
- Replace `Files.delete(...)` with `Files.deleteIfExists(...)`.
- Wrap deletion in its own try/catch; log on failure but still return `OcrResult.success(outputPdf)` once the PDF save succeeds.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

6. /// uses {@link ...} ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
New Markdown Javadoc comments use JavaDoc inline tags like {@link OcrEngine} instead of Markdown
link syntax, which violates the project’s documentation convention. This reduces consistency and
readability across the codebase.
Code

jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[31]

+/// Implementation of the {@link OcrEngine} interface using Docling.
Evidence
PR Compliance ID 15 requires Markdown Javadoc comments to use Markdown conventions instead of
JavaDoc inline tags. The new class comment uses {@link OcrEngine} within a /// comment.

AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them: AGENTS.md: Use Markdown Javadoc comments (///) for multi-line comments and Markdown syntax inside them
jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[31-32]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Markdown Javadoc (`///`) should use Markdown link syntax (e.g., `[OcrEngine]`) rather than JavaDoc inline tags like `{@link ...}`.
## Issue Context
This repository standardizes on Markdown-based Javadoc comments.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java[31-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread jabgui/src/main/java/org/jabref/gui/linkedfile/OcrLinkedFileAction.java Outdated
Comment thread jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java Outdated
Comment thread jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java Outdated
Comment thread jablib/src/main/java/org/jabref/model/ocr/docling/DoclingProv.java
Comment thread jablib/src/main/java/org/jabref/model/ocr/docling/DoclingText.java

@InAnYan InAnYan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, for now to move forward, the most critical points are:

  • Move some classes to model layer
  • There should not be commented out code
  • Look what happens here - it reads JSON, but what if there will be a parsing error? Maybe add try/catch block. Or the error is IOException?
  • Look at Qodo suggestions

@InAnYan

InAnYan commented Jul 29, 2026

Copy link
Copy Markdown
Member

I edited the comment because I hit Enter too quickly

this.preferences = preferences;
this.taskExecutor = taskExecutor;
this.ocrEngine = new OcrMyPdfEngine(preferences.getOcrPreferences());
// this.ocrEngine = new OcrMyPdfEngine(preferences.getOcrPreferences());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this as well.

@subhramit subhramit left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your learning curve is really visible - I didn't have to repeat any comment I had in your older PRs :)

}

private OcrResult embedText(Path jsonOutputPath, Path originalPdf) throws IOException {
JsonMapper jsonMapper = new JsonMapper();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if this class is used once, we should store this initialization as a private static final field for subsequent uses and not initialize every time on this method call.

Comment on lines +138 to +139
PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
float fontSize = 12F;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for the font and fontSize - it is fixed for every time this method is called in this class.
Move to class constants, don't re-initialize.

@ZiadAbdElFatah

Copy link
Copy Markdown
Collaborator Author
  • Look what happens here - it reads JSON, but what if there will be a parsing error? Maybe add try/catch block. Or the error is IOException?

It throws JacksonIOException, would be handled by the normal IOException?

@subhramit

subhramit commented Aug 1, 2026

Copy link
Copy Markdown
Member

It throws JacksonIOException, would be handled by the normal IOException?

any XYZIOException should be a subset of IOException unless they just named it that way. But work with specific exceptions as much as you can. Whatever is true for IOException should be true for this as well.

Example - #16452 (comment) (not a very good pattern but just for reference)

Comment thread jablib/src/main/java/org/jabref/logic/ocr/docling/DoclingEngine.java Outdated
@github-actions github-actions Bot added component: external-files component: external-application-integration Ingegeration with TeXStudio, Acrobat Reader, ... labels Aug 1, 2026

@subhramit subhramit left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😎

@github-actions github-actions Bot added status: no-bot-comments and removed status: changes-required Pull requests that are not yet complete labels Aug 1, 2026

@InAnYan InAnYan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, good! For now we can move on

/// @return true if the engine is available, false otherwise.
public static boolean isAvailable(OcrPreferences ocrPreferences) {
ArrayList<String> command = StringUtil.splitRespectingEscapedWhitespace(ocrPreferences.getOcrEnginePath());
command.add("--version");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably leave it in the OcrEngine interface, because each specific engine might have their own arguments to check (--help, --version, --v, etc.). And what if there won't be a long version for "version"?

I would leave the logic to run the process in ocr utils

@InAnYan
InAnYan added this pull request to the merge queue Aug 1, 2026
@github-actions github-actions Bot added the status: to-be-merged PRs which are accepted and should go into the merge-queue. label Aug 1, 2026
Merged via the queue into JabRef:main with commit 671b99e Aug 1, 2026
69 checks passed
@ZiadAbdElFatah
ZiadAbdElFatah deleted the docling-engine branch August 1, 2026 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component: external-application-integration Ingegeration with TeXStudio, Acrobat Reader, ... component: external-files component: ocr project: gsoc status: no-bot-comments status: to-be-merged PRs which are accepted and should go into the merge-queue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GSoC meta issue: OCR Integration in JabRef

3 participants