Foxit LLM OCR Server-SideBeta 0.3

OCR with Foxit’s Next-Gen LLM Engine

Drop in a scan and it’s recognized server-side by Foxit’s LLM-based OCR engine — an unreleased, next-generation engine demoed here running GPU-accelerated (ONNX Runtime + CUDA on an RTX 3080 Ti). You get back a searchable PDF and the extracted text, with a page viewer to review the result.

Drop PDF or images here, or click to upload

Supports PDF, TIFF, JPG, BMP, PNG — processed server-side with Foxit’s LLM OCR engine

How it works

1 — Upload. The browser posts your file to a thin PHP proxy on this web server, which forwards it to a FastAPI service on a Windows GPU workstation. The job starts in the background and immediately returns a session id.

2 — Recognition. The service runs Foxit’s LLM-based OCR engine — a next-generation engine that is not yet a released Foxit product, demoed here ahead of availability. Inference is GPU-accelerated via ONNX Runtime with CUDA on an RTX 3080 Ti; the model reads whole page layouts rather than isolated characters, which is what gives it its accuracy on messy scans.

3 — Results. The page polls the status endpoint every few seconds while the engine works. When the job is ready it fetches the result payload — full text plus per-word geometry for this viewer — and offers the searchable PDF (your original page images with an invisible text layer) and the plain text for download.

Because the engine is driven over REST, any language that can send a multipart POST can integrate it — the samples below run against this demo’s own endpoints.

Sample integration code

The exact flow this page uses — upload, poll, download — as plain REST calls. Condensed for clarity.

API=https://jeffbrand.ca/FoxitOCR/api.php

# 1. Upload — starts a background OCR job, returns a session id
SESSION=$(curl -s -F action=ocr -F language=eng -F file=@scan.pdf "$API" | jq -r .session)

# 2. Poll until the engine reports ready
until curl -s "$API?action=status&session=$SESSION" | jq -e '.ready == true' >/dev/null; do
  sleep 3
done

# 3. Download the searchable PDF and the extracted text
curl -o searchable.pdf "$API?action=download_pdf&session=$SESSION"
curl -o result.txt     "$API?action=download_text&session=$SESSION"
import time, requests

API = 'https://jeffbrand.ca/FoxitOCR/api.php'

# 1. Upload — starts a background OCR job, returns a session id
with open('scan.pdf', 'rb') as f:
    r = requests.post(API, data={'action': 'ocr', 'language': 'eng'},
                      files={'file': ('scan.pdf', f, 'application/pdf')})
r.raise_for_status()
session = r.json()['session']

# 2. Poll until the engine reports ready
while True:
    status = requests.get(API, params={'action': 'status', 'session': session}).json()
    if status.get('error'):
        raise RuntimeError(status['error'])
    if status.get('ready'):
        break
    time.sleep(3)

# 3. Download the searchable PDF and the extracted text
pdf = requests.get(API, params={'action': 'download_pdf', 'session': session})
open('searchable.pdf', 'wb').write(pdf.content)
txt = requests.get(API, params={'action': 'download_text', 'session': session})
open('result.txt', 'wb').write(txt.content)
const API = 'https://jeffbrand.ca/FoxitOCR/api.php';

// 1. Upload — starts a background OCR job, returns a session id
const fd = new FormData();
fd.append('action', 'ocr');
fd.append('language', 'eng');
fd.append('file', fileInput.files[0]);          // a File from an <input type="file">
const { session } = await (await fetch(API, { method: 'POST', body: fd })).json();

// 2. Poll until the engine reports ready
for (;;) {
  const status = await (await fetch(`${API}?action=status&session=${session}`)).json();
  if (status.error) throw new Error(status.error);
  if (status.ready) break;
  await new Promise(r => setTimeout(r, 3000));
}

// 3. Download the searchable PDF (and/or the extracted text)
const pdfBlob = await (await fetch(`${API}?action=download_pdf&session=${session}`)).blob();
const text    = await (await fetch(`${API}?action=download_text&session=${session}`)).text();
using System.Net.Http;
using System.Text.Json;

const string Api = "https://jeffbrand.ca/FoxitOCR/api.php";
using var http = new HttpClient();

// 1. Upload — starts a background OCR job, returns a session id
using var form = new MultipartFormDataContent {
    { new StringContent("ocr"), "action" },
    { new StringContent("eng"), "language" },
    { new ByteArrayContent(File.ReadAllBytes("scan.pdf")), "file", "scan.pdf" },
};
var upload = JsonDocument.Parse(
    await (await http.PostAsync(Api, form)).Content.ReadAsStringAsync());
string session = upload.RootElement.GetProperty("session").GetString()!;

// 2. Poll until the engine reports ready
while (true) {
    var status = JsonDocument.Parse(
        await http.GetStringAsync($"{Api}?action=status&session={session}"));
    if (status.RootElement.TryGetProperty("ready", out var r) && r.GetBoolean()) break;
    await Task.Delay(3000);
}

// 3. Download the searchable PDF and the extracted text
File.WriteAllBytes("searchable.pdf",
    await http.GetByteArrayAsync($"{Api}?action=download_pdf&session={session}"));
File.WriteAllText("result.txt",
    await http.GetStringAsync($"{Api}?action=download_text&session={session}"));
import java.net.URI;
import java.net.http.*;
import java.nio.file.*;

String api = "https://jeffbrand.ca/FoxitOCR/api.php";
HttpClient http = HttpClient.newHttpClient();

// 1. Upload — multipart/form-data body built by hand (or use a helper library)
String boundary = "----ocr" + System.nanoTime();
byte[] pdf = Files.readAllBytes(Path.of("scan.pdf"));
var body = new java.io.ByteArrayOutputStream();
for (String[] field : new String[][] {{"action", "ocr"}, {"language", "eng"}}) {
    body.writeBytes(("--" + boundary + "\r\nContent-Disposition: form-data; name=\""
        + field[0] + "\"\r\n\r\n" + field[1] + "\r\n").getBytes());
}
body.writeBytes(("--" + boundary + "\r\nContent-Disposition: form-data; name=\"file\";"
    + " filename=\"scan.pdf\"\r\nContent-Type: application/pdf\r\n\r\n").getBytes());
body.writeBytes(pdf);
body.writeBytes(("\r\n--" + boundary + "--\r\n").getBytes());

HttpRequest upload = HttpRequest.newBuilder(URI.create(api))
    .header("Content-Type", "multipart/form-data; boundary=" + boundary)
    .POST(HttpRequest.BodyPublishers.ofByteArray(body.toByteArray())).build();
String json = http.send(upload, HttpResponse.BodyHandlers.ofString()).body();
String session = json.replaceAll(".*\"session\"\\s*:\\s*\"([^\"]+)\".*", "$1");

// 2. Poll until the engine reports ready
while (true) {
    String status = http.send(HttpRequest.newBuilder(
            URI.create(api + "?action=status&session=" + session)).build(),
        HttpResponse.BodyHandlers.ofString()).body();
    if (status.contains("\"ready\":true")) break;
    Thread.sleep(3000);
}

// 3. Download the searchable PDF
http.send(HttpRequest.newBuilder(
        URI.create(api + "?action=download_pdf&session=" + session)).build(),
    HttpResponse.BodyHandlers.ofFile(Path.of("searchable.pdf")));

Samples are condensed for clarity — production code should time-box the polling loop and handle upload errors. The engine behind this API is an unreleased, next-generation Foxit OCR engine; this demo endpoint exists to show the integration shape, not as a hosted service.