Управление OCR-моделями в офлайн-режиме
OCR в PDF Oxide использует ONNX-модели детектора и распознавания, хранящиеся в локальном каталоге кеша. Для сборок в Docker, CI и изолированных / офлайн-окружений эти модели должны быть готовы до первого OCR-вызова — загрузка во время запроса недопустима. PDF Oxide предоставляет для этого три примитива:
prefetch_models— загружает общий детектор, а также модель распознавания и словарь для каждого языка в каталог кеша моделей (подготовка на этапе сборки).model_manifest— JSON-манифест всех файлов моделей и их исходных URL без обращения к сети; используется для зеркалирования и верификации на изолированных хостах.prefetch_available— возвращает, способна ли данная сборка загружать модели (скомпилирована ли с фичейocr).
Каталог кеша задаётся переменной $PDF_OXIDE_MODEL_DIR, а при её отсутствии используется платформенный кеш (~/.cache/pdf_oxide/models на Linux). Когда файлы на месте, OCR работает полностью в офлайн-режиме.
Охват привязок. Подготовка моделей доступна в Rust, Go, C# и Swift.
model_manifestиprefetch_availableтакже доступны в WASM/JavaScript (гдеprefetchAvailable()всегда возвращаетfalse— в WASM нет сетевого загрузчика, поэтому файлы подготавливаются на хосте с помощью манифеста). Привязки Python и Node N-API не предоставляют этих функций в v0.3.69.
Как предзагрузить OCR-модели для офлайн-работы?
prefetch_models принимает языковые коды через запятую (пустая строка означает английский), загружает общий детектор, модель распознавания и словарь каждого языка в каталог кеша и возвращает его путь. Функция идемпотентна — уже существующие файлы пропускаются.
Rust
use pdf_oxide::extractors::auto::{AutoExtractor, OcrLanguage};
fn main() -> pdf_oxide::Result<()> {
// AutoExtractor::prefetch_models(langs: &[OcrLanguage])
// -> Result<std::path::PathBuf>
let dir = AutoExtractor::prefetch_models(&[
OcrLanguage::English,
OcrLanguage::Chinese,
OcrLanguage::Arabic,
])?;
println!("models cached in {}", dir.display());
// One-shot English (the common case):
let _ = AutoExtractor::prefetch_models_default()?;
Ok(())
}
Go
package main
import (
"fmt"
"log"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
func main() {
if !pdfoxide.PrefetchAvailable() {
log.Fatal("this build cannot download models (built without the ocr feature)")
}
// func PrefetchModels(langs ...string) (string, error)
dir, err := pdfoxide.PrefetchModels("english", "chinese", "arabic")
if err != nil {
log.Fatal(err)
}
fmt.Println("models cached in", dir)
}
C#
using System;
using PdfOxide.Core;
if (!PdfDocument.PrefetchAvailable())
throw new InvalidOperationException("built without the ocr feature; cannot download models");
// static string PdfDocument.PrefetchModels(params string[] languages)
string dir = PdfDocument.PrefetchModels("english", "chinese", "arabic");
Console.WriteLine($"models cached in {dir}");
Swift
import PdfOxide
guard PdfOxide.prefetchAvailable() == 1 else {
fatalError("built without the ocr feature; cannot download models")
}
// static func prefetchModels(languagesCsv: String) throws -> String
let dir = try PdfOxide.prefetchModels(languagesCsv: "english,chinese,arabic")
print("models cached in \(dir)")
PHP
use PdfOxide\Pdf;
if (!Pdf::prefetchAvailable()) {
throw new RuntimeException('built without the ocr feature; cannot download models');
}
// static Pdf::prefetchModels(array $languages): string
$dir = Pdf::prefetchModels(['english', 'chinese', 'arabic']);
echo "models cached in {$dir}\n";
Ruby
require 'pdf_oxide'
unless PdfOxide::Pdf.prefetch_available?
raise 'built without the ocr feature; cannot download models'
end
# PdfOxide::Pdf.prefetch_models(languages) -> String (cache dir)
dir = PdfOxide::Pdf.prefetch_models(%w[english chinese arabic])
puts "models cached in #{dir}"
C++
#include <pdf_oxide/pdf_oxide.hpp>
#include <iostream>
if (pdf_oxide::prefetch_available() == 0)
throw std::runtime_error("built without the ocr feature; cannot download models");
// std::string pdf_oxide::prefetch_models(const std::string& languages_csv)
auto dir = pdf_oxide::prefetch_models("english,chinese,arabic");
std::cout << "models cached in " << dir << "\n";
Dart
import 'package:pdf_oxide/pdf_oxide.dart' as pdf_oxide;
if (pdf_oxide.prefetchAvailable() == 0) {
throw StateError('built without the ocr feature; cannot download models');
}
// String prefetchModels(String languagesCsv)
final dir = pdf_oxide.prefetchModels('english,chinese,arabic');
print('models cached in $dir');
R
library(pdfoxide)
if (pdf_prefetch_available() == 0)
stop("built without the ocr feature; cannot download models")
# pdf_prefetch_models(languages_csv = NULL) -> cache directory path
dir <- pdf_prefetch_models("english,chinese,arabic")
cat("models cached in", dir, "\n")
Julia
using PdfOxide
prefetch_available() != 0 || error("built without the ocr feature; cannot download models")
# prefetch_models(languages_csv::AbstractString) -> cache directory path
dir = prefetch_models("english,chinese,arabic")
println("models cached in ", dir)
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
if (pdf_oxide.prefetchAvailable() == 0) return error.OcrFeatureMissing;
// prefetchModels(alloc, languages_csv) -> []u8 (cache dir; caller frees)
const dir = try pdf_oxide.prefetchModels(a, "english,chinese,arabic");
defer a.free(dir);
std.debug.print("models cached in {s}\n", .{dir});
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
if ([POXModels prefetchAvailable] <= 0) {
@throw [NSException exceptionWithName:@"PdfOxide" reason:@"no ocr feature" userInfo:nil];
}
// + prefetchModels:error: returns a status JSON (nil on error)
NSString *status = [POXModels prefetchModels:@"english,chinese,arabic" error:&err];
NSLog(@"prefetch status: %@", status);
Elixir
unless PdfOxide.prefetch_available() != 0 do
raise "built without the ocr feature; cannot download models"
end
# prefetch_models(languages_csv \\ "") -> JSON status string
status = PdfOxide.prefetch_models("english,chinese,arabic")
IO.puts("prefetch status: #{status}")
Коды языков
prefetch_models принимает следующие коды (неизвестные коды пропускаются; пустой ввод по умолчанию означает английский):
english, chinese, chinese_cht, japan, korean, arabic, cyrillic, latin, devanagari, ta (тамильский), te (телугу), ka (каннада).
Как подготовить модели для изолированного хоста?
На машине без интернета (или для WASM-цели без загрузчика) вызвать prefetch_models невозможно. Вместо этого используйте model_manifest — статический JSON-список всех файлов моделей и их исходных URL без обращения к сети. Скачайте файлы через артефактное хранилище и разместите их в $PDF_OXIDE_MODEL_DIR.
Rust
use pdf_oxide::extractors::auto::AutoExtractor;
fn main() {
// AutoExtractor::model_manifest() -> String (JSON, never errors)
let manifest = AutoExtractor::model_manifest();
println!("{manifest}");
}
Go
// func ModelManifest() string (JSON, never errors)
fmt.Println(pdfoxide.ModelManifest())
C#
// static string PdfDocument.ModelManifest()
Console.WriteLine(PdfDocument.ModelManifest());
Swift
// static func modelManifest() -> String (JSON)
print(PdfOxide.modelManifest())
JavaScript (WASM)
import init, { modelManifest, prefetchAvailable } from "pdf-oxide-wasm";
await init();
// prefetchAvailable() is always false in WASM — provision host-side.
console.log("can download here?", prefetchAvailable()); // false
console.log(modelManifest()); // JSON manifest
PHP
use PdfOxide\FFI\FunctionBindings;
// (new FunctionBindings())->pdfOxideModelManifest(): string (JSON, never errors)
$manifest = (new FunctionBindings())->pdfOxideModelManifest();
echo $manifest, "\n";
C++
#include <pdf_oxide/pdf_oxide.hpp>
#include <iostream>
// std::string pdf_oxide::model_manifest() (JSON, never errors)
std::cout << pdf_oxide::model_manifest() << "\n";
Dart
import 'package:pdf_oxide/pdf_oxide.dart' as pdf_oxide;
// String modelManifest() (JSON)
print(pdf_oxide.modelManifest());
R
library(pdfoxide)
# pdf_model_manifest() -> JSON string
cat(pdf_model_manifest(), "\n")
Julia
using PdfOxide
# model_manifest() -> JSON String
println(model_manifest())
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
// modelManifest(alloc) -> []u8 (JSON; caller frees)
const manifest = try pdf_oxide.modelManifest(a);
defer a.free(manifest);
std.debug.print("{s}\n", .{manifest});
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
// + manifestWithError: -> JSON string (nil on error)
NSString *manifest = [POXModels manifestWithError:&err];
NSLog(@"%@", manifest);
Elixir
# model_manifest() -> JSON string
IO.puts(PdfOxide.model_manifest())
Как выглядит манифест?
{
"detector": {
"file": "det.onnx",
"url": "https://.../det.onnx"
},
"languages": [
{
"language": "english",
"rec_file": "rec.onnx",
"dict_file": "en_dict.txt",
"rec_url": "https://.../rec.onnx",
"dict_url": "https://.../en_dict.txt"
}
],
"note": "Hebrew has no upstream PaddleOCR recognition model; the loader is ready if one is provided."
}
Скачайте detector.url и rec_url / dict_url каждого языка, затем разместите det.onnx и каждый rec_file / dict_file в PDF_OXIDE_MODEL_DIR. После этого OCR работает без какого-либо сетевого доступа.
Поддерживает ли данная сборка загрузку моделей?
prefetch_available сообщает, скомпилирована ли нативная библиотека с фичей ocr (которая включает HTTP-загрузчик). Если возвращается false, prefetch_models всё равно создаст каталог кеша, но не выполнит загрузку — проверяйте это перед тем, как рассчитывать на загрузку.
Rust
use pdf_oxide::extractors::auto::AutoExtractor;
// AutoExtractor::prefetch_available() -> bool
if AutoExtractor::prefetch_available() {
let _ = AutoExtractor::prefetch_models_default();
} else {
eprintln!("OCR feature not compiled in — provision via model_manifest()");
}
Go — pdfoxide.PrefetchAvailable() bool
C# — PdfDocument.PrefetchAvailable() -> bool
Swift — PdfOxide.prefetchAvailable() -> Int32 (1 == yes)
Пример Dockerfile
Запекайте модели в образ на этапе сборки, чтобы работающий контейнер никогда не обращался к сети:
FROM rust:1 AS models
WORKDIR /app
COPY . .
# Build the CLI / your binary with the `ocr` feature, then prefetch.
ENV PDF_OXIDE_MODEL_DIR=/models
RUN cargo run --features ocr --bin prefetch -- english chinese
FROM debian:stable-slim
ENV PDF_OXIDE_MODEL_DIR=/models
COPY --from=models /models /models
# OCR now runs fully offline against /models
Глобальные настройки движка
Два глобальных сеттера уровня процесса позволяют настроить движок извлечения. Оба доступны через C-ABI привязки, оба возвращают предыдущее значение и оба не имеют канала ошибок (не могут завершиться с ошибкой). Поскольку они глобальны для процесса, изменение в одном потоке влияет на все параллельные операции извлечения.
Как поднять ограничение операторов контентного потока?
PDF Oxide ограничивает число операторов контентного потока на один поток (по умолчанию 1 000 000), чтобы ограничить стоимость обработки вредоносных входных данных. Большие легитимные технические PDF (учебники, стандарты ISO) могут превышать этот лимит. set_max_ops_per_stream повышает (или понижает) ограничение и возвращает предыдущее значение.
Rust
// pdf_oxide::content::parser::set_max_ops_per_stream(limit: Option<usize>)
// -> Option<usize> (None restores the 1,000,000 default)
use pdf_oxide::content::parser::set_max_ops_per_stream;
let prev = set_max_ops_per_stream(Some(5_000_000));
// ... extract a huge trusted PDF ...
set_max_ops_per_stream(prev); // restore
Go
// func SetMaxOpsPerStream(limit int64) int64 (returns previous cap)
prev := pdfoxide.SetMaxOpsPerStream(5_000_000)
defer pdfoxide.SetMaxOpsPerStream(prev)
C#
// static long CAbi.SetMaxOpsPerStream(long limit) (returns previous cap)
long prev = PdfOxide.Core.CAbi.SetMaxOpsPerStream(5_000_000);
try { /* extract huge trusted PDF */ }
finally { PdfOxide.Core.CAbi.SetMaxOpsPerStream(prev); }
Swift
// static func setMaxOpsPerStream(_ limit: Int64) -> Int64
let prev = PdfOxide.setMaxOpsPerStream(5_000_000)
defer { _ = PdfOxide.setMaxOpsPerStream(prev) }
PHP
use PdfOxide\FFI\FunctionBindings;
$bindings = new FunctionBindings();
// pdfOxideSetMaxOpsPerStream(int $limit): int (returns previous cap; -1 = default was active)
$prev = $bindings->pdfOxideSetMaxOpsPerStream(5_000_000);
try { /* extract huge trusted PDF */ }
finally { $bindings->pdfOxideSetMaxOpsPerStream($prev); }
Ruby
require 'pdf_oxide'
# PdfOxide.set_max_ops_per_stream(limit) -> previous cap (-1 = default was active)
prev = PdfOxide.set_max_ops_per_stream(5_000_000)
begin
# ... extract a huge trusted PDF ...
ensure
PdfOxide.set_max_ops_per_stream(prev)
end
C++
#include <pdf_oxide/pdf_oxide.hpp>
// std::int64_t pdf_oxide::set_max_ops_per_stream(std::int64_t limit) -> previous cap
auto prev = pdf_oxide::set_max_ops_per_stream(5'000'000);
// ... extract a huge trusted PDF ...
pdf_oxide::set_max_ops_per_stream(prev); // restore
Dart
import 'package:pdf_oxide/pdf_oxide.dart' as pdf_oxide;
// int setMaxOpsPerStream(int limit) -> previous cap
final prev = pdf_oxide.setMaxOpsPerStream(5000000);
// ... extract a huge trusted PDF ...
pdf_oxide.setMaxOpsPerStream(prev); // restore
R
library(pdfoxide)
# pdf_set_max_ops_per_stream(limit) -> previous cap (negative limit restores default)
prev <- pdf_set_max_ops_per_stream(5000000)
# ... extract a huge trusted PDF ...
pdf_set_max_ops_per_stream(prev) # restore
Julia
using PdfOxide
# set_max_ops_per_stream(limit::Integer) -> previous cap
prev = set_max_ops_per_stream(5_000_000)
# ... extract a huge trusted PDF ...
set_max_ops_per_stream(prev) # restore
Zig
const pdf_oxide = @import("pdf_oxide");
// setMaxOpsPerStream(limit: i64) i64 (returns previous cap)
const prev = pdf_oxide.setMaxOpsPerStream(5_000_000);
// ... extract a huge trusted PDF ...
_ = pdf_oxide.setMaxOpsPerStream(prev); // restore
Objective-C
#import "POXPdfOxide.h"
// + setMaxOpsPerStream: -> previous cap
int64_t prev = [POXConfig setMaxOpsPerStream:5000000];
// ... extract a huge trusted PDF ...
[POXConfig setMaxOpsPerStream:prev]; // restore
Elixir
# set_max_ops_per_stream(limit) -> previous cap (-1 = default was active)
prev = PdfOxide.set_max_ops_per_stream(5_000_000)
# ... extract a huge trusted PDF ...
PdfOxide.set_max_ops_per_stream(prev)
На уровне C ABI функция pdf_oxide_set_max_ops_per_stream(limit) трактует отрицательное значение limit как «восстановить значение по умолчанию» и возвращает -1, если ранее было активно значение по умолчанию.
Как сохранять неотображаемые глифы (U+FFFD)?
По умолчанию высокоуровневые аксессоры (extract_text / extract_words / extract_spans) фильтруют глифы без Unicode-отображения (они выводились бы как U+FFFD �). На страницах, где все видимые глифы отображаются в U+FFFD — например, математический шрифт MSAM10 — это может приводить к пустому результату. set_preserve_unmapped_glyphs(true) заставляет аксессоры сохранять символы замены, чтобы их можно было увидеть и обработать; возвращается предыдущее значение настройки.
Rust
// pdf_oxide::extractors::text::set_preserve_unmapped_glyphs(preserve: bool)
// -> bool (returns previous value)
use pdf_oxide::extractors::text::set_preserve_unmapped_glyphs;
let prev = set_preserve_unmapped_glyphs(true);
// ... extract a math-heavy PDF; U+FFFD glyphs are now kept ...
set_preserve_unmapped_glyphs(prev);
Go
// func SetPreserveUnmappedGlyphs(preserve int) int (1 = preserve; returns previous)
prev := pdfoxide.SetPreserveUnmappedGlyphs(1)
defer pdfoxide.SetPreserveUnmappedGlyphs(prev)
C#
// static int CAbi.SetPreserveUnmappedGlyphs(bool preserve) (returns previous, 0/1)
int prev = PdfOxide.Core.CAbi.SetPreserveUnmappedGlyphs(true);
try { /* extract math-heavy PDF */ }
finally { PdfOxide.Core.CAbi.SetPreserveUnmappedGlyphs(prev != 0); }
Swift
// static func setPreserveUnmappedGlyphs(_ preserve: Int32) -> Int32
let prev = PdfOxide.setPreserveUnmappedGlyphs(1)
defer { _ = PdfOxide.setPreserveUnmappedGlyphs(prev) }
PHP
use PdfOxide\FFI\FunctionBindings;
$bindings = new FunctionBindings();
// pdfOxideSetPreserveUnmappedGlyphs(int $preserve): int (1 = preserve; returns previous, 0/1)
$prev = $bindings->pdfOxideSetPreserveUnmappedGlyphs(1);
try { /* extract math-heavy PDF */ }
finally { $bindings->pdfOxideSetPreserveUnmappedGlyphs($prev); }
Ruby
require 'pdf_oxide'
# PdfOxide.set_preserve_unmapped_glyphs(preserve) -> previous value (0 or 1)
prev = PdfOxide.set_preserve_unmapped_glyphs(true)
begin
# ... extract a math-heavy PDF; U+FFFD glyphs are now kept ...
ensure
PdfOxide.set_preserve_unmapped_glyphs(prev)
end
C++
#include <pdf_oxide/pdf_oxide.hpp>
// int pdf_oxide::set_preserve_unmapped_glyphs(int preserve) -> previous value
int prev = pdf_oxide::set_preserve_unmapped_glyphs(1);
// ... extract a math-heavy PDF; U+FFFD glyphs are now kept ...
pdf_oxide::set_preserve_unmapped_glyphs(prev); // restore
Dart
import 'package:pdf_oxide/pdf_oxide.dart' as pdf_oxide;
// int setPreserveUnmappedGlyphs(int preserve) -> previous value
final prev = pdf_oxide.setPreserveUnmappedGlyphs(1);
// ... extract a math-heavy PDF; U+FFFD glyphs are now kept ...
pdf_oxide.setPreserveUnmappedGlyphs(prev); // restore
R
library(pdfoxide)
# pdf_set_preserve_unmapped_glyphs(preserve) -> previous value (0 or 1)
prev <- pdf_set_preserve_unmapped_glyphs(1L)
# ... extract a math-heavy PDF; U+FFFD glyphs are now kept ...
pdf_set_preserve_unmapped_glyphs(prev) # restore
Julia
using PdfOxide
# set_preserve_unmapped_glyphs(preserve::Integer) -> previous value (0 or 1)
prev = set_preserve_unmapped_glyphs(1)
# ... extract a math-heavy PDF; U+FFFD glyphs are now kept ...
set_preserve_unmapped_glyphs(prev) # restore
Zig
const pdf_oxide = @import("pdf_oxide");
// setPreserveUnmappedGlyphs(preserve: bool) i32 (returns previous value)
const prev = pdf_oxide.setPreserveUnmappedGlyphs(true);
// ... extract a math-heavy PDF; U+FFFD glyphs are now kept ...
_ = pdf_oxide.setPreserveUnmappedGlyphs(prev != 0); // restore
Objective-C
#import "POXPdfOxide.h"
// + setPreserveUnmappedGlyphs: -> previous value (0 or 1)
int32_t prev = [POXConfig setPreserveUnmappedGlyphs:1];
// ... extract a math-heavy PDF; U+FFFD glyphs are now kept ...
[POXConfig setPreserveUnmappedGlyphs:prev]; // restore
Elixir
# set_preserve_unmapped_glyphs(preserve) -> previous value (0 or 1)
prev = PdfOxide.set_preserve_unmapped_glyphs(1)
# ... extract a math-heavy PDF; U+FFFD glyphs are now kept ...
PdfOxide.set_preserve_unmapped_glyphs(prev)
На уровне C ABI функция pdf_oxide_set_preserve_unmapped_glyphs(preserve) принимает 1 для сохранения / 0 для фильтрации и возвращает предыдущее значение как 0 или 1.
Часто задаваемые вопросы
Где хранятся OCR-модели?
В $PDF_OXIDE_MODEL_DIR, если задана, иначе в платформенном кеше (~/.cache/pdf_oxide/models на Linux). Этот путь также возвращает prefetch_models.
Безопасно ли вызывать prefetch_models повторно?
Да — функция идемпотентна. Существующие файлы пропускаются, поэтому её можно вызывать при каждом старте как страховку.
Почему prefetch_available возвращает false, хотя я вызвал prefetch?
Сборка скомпилирована без фичи ocr, поэтому HTTP-загрузчик отсутствует. prefetch_models всё равно создаст каталог кеша, но ничего не загрузит — подготовьте файлы вручную с помощью model_manifest.
Нужно ли сбрасывать глобальные сеттеры? Они действуют на уровне всего процесса и сохраняют значение до явного изменения. Если переопределение нужно только для одного документа — восстановите предыдущее значение (его возвращает каждый сеттер). Оба сеттера не могут завершиться с ошибкой и не имеют канала ошибок.
Связанные страницы
- OCR сканированных PDF — запуск OCR после подготовки моделей
- Классификация страниц — определение, каким страницам нужен OCR
- Логирование и отладочный вывод — другие глобальные настройки библиотеки
- Извлечение текста из PDF — высокоуровневые аксессоры, на которые влияет
set_preserve_unmapped_glyphs