Skip to content

Offline-OCR-Modellverwaltung

Die OCR in PDF Oxide verwendet ONNX-Erkennungs- und Detektionsmodelle, die in einem lokalen Cache-Verzeichnis gespeichert sind. Für Docker-Builds, CI und **Air-Gapped- / Offline-**Deployments müssen diese Modelle vor dem ersten OCR-Aufruf bereitstehen — ein Download zur Laufzeit ist nicht akzeptabel. PDF Oxide bietet dafür drei Grundbausteine:

  • prefetch_models — lädt den gemeinsamen Detektor sowie das Erkennungsmodell und das Wörterbuch für jede Sprache in das Cache-Verzeichnis herunter (Bereitstellung zur Build-Zeit).
  • model_manifest — ein netzwerkfreies JSON-Manifest aller Modelldateien und ihrer Quell-URLs zur Spiegelung und Überprüfung auf einem Air-Gapped-Host.
  • prefetch_available — gibt an, ob dieser Build tatsächlich herunterladen kann (mit dem Feature ocr kompiliert).

Das Cache-Verzeichnis ist $PDF_OXIDE_MODEL_DIR, falls gesetzt, andernfalls der Plattform-Cache (~/.cache/pdf_oxide/models unter Linux). Sobald die Dateien vorhanden sind, läuft die OCR vollständig offline.

Binding-Abdeckung. Die Modellbereitstellung steht in Rust, Go, C# und Swift zur Verfügung. model_manifest und prefetch_available sind außerdem in WASM/JavaScript verfügbar (wo prefetchAvailable() stets false zurückgibt — WASM verfügt über keinen Netzwerk-Fetcher, daher erfolgt die Bereitstellung hostseitig mithilfe des Manifests). Die Python- und Node-N-API-Bindings legen diese Funktionen in v0.3.69 nicht frei.

Wie lade ich OCR-Modelle für den Offline-Betrieb vorab?

prefetch_models akzeptiert kommagetrennte Sprachcodes (leer = Englisch), lädt den gemeinsamen Detektor sowie das Erkennungsmodell und das Wörterbuch jeder Sprache in das Cache-Verzeichnis und gibt dessen Pfad zurück. Die Funktion ist idempotent — bereits vorhandene Dateien werden übersprungen.

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}")

Sprachcodes

prefetch_models akzeptiert folgende Codes (unbekannte Codes werden übersprungen; leere Eingabe verwendet Englisch als Standard):

english, chinese, chinese_cht, japan, korean, arabic, cyrillic, latin, devanagari, ta (Tamil), te (Telugu), ka (Kannada)

Wie stelle ich Modelle auf einem Air-Gapped-Host bereit?

Auf einem Rechner ohne Internet (oder einem WASM-Target ohne Fetcher) kann prefetch_models nicht aufgerufen werden. Lesen Sie stattdessen model_manifest — eine statische, netzwerkfreie JSON-Auflistung aller Modelldateien und ihrer Upstream-URLs. Spiegeln Sie diese URLs über Ihren Artefakt-Store und legen Sie die Dateien in $PDF_OXIDE_MODEL_DIR ab.

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())

Wie sieht das Manifest aus?

{
  "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."
}

Spiegeln Sie detector.url und jeweils rec_url / dict_url jeder Sprache, und legen Sie dann det.onnx sowie jede rec_file / dict_file in Ihrem PDF_OXIDE_MODEL_DIR ab. Danach läuft die OCR ohne Netzwerkzugriff.

Unterstützt dieser Build den Modell-Download?

prefetch_available gibt an, ob die native Bibliothek mit dem Feature ocr (das den HTTP-Fetcher einschließt) kompiliert wurde. Wenn false zurückgegeben wird, erstellt prefetch_models zwar das Cache-Verzeichnis, führt aber keinen Download durch — prüfen Sie das, bevor Sie auf einen Fetch angewiesen sind.

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()");
}

Gopdfoxide.PrefetchAvailable() bool C#PdfDocument.PrefetchAvailable() -> bool SwiftPdfOxide.prefetchAvailable() -> Int32 (1 == yes)

Dockerfile-Beispiel

Baken Sie die Modelle zur Build-Zeit in das Image ein, damit der laufende Container niemals auf das Netzwerk zugreift:

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

Globale Engine-Konfiguration

Zwei prozessglobale Setter steuern die Extraktions-Engine. Beide werden über die C-ABI-Bindings bereitgestellt, beide geben den vorherigen Wert zurück und beide haben keinen Fehlerkanal (sie können nicht fehlschlagen). Da sie prozessglobal sind, wirkt sich eine Änderung in einem Thread auf alle gleichzeitig laufenden Extraktionen aus.

Wie erhöhe ich das Limit für Content-Stream-Operatoren?

PDF Oxide begrenzt die Anzahl der Content-Stream-Operatoren pro Stream (Standard: 1.000.000), um den Rechenaufwand bei adversarialen Eingaben zu deckeln. Große legitime technische PDFs (Lehrbücher, ISO-Normen) können diesen Wert überschreiten. set_max_ops_per_stream erhöht (oder senkt) das Limit und gibt den vorherigen Wert zurück.

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)

Auf C-ABI-Ebene behandelt pdf_oxide_set_max_ops_per_stream(limit) einen negativen limit-Wert als „Standard wiederherstellen" und gibt -1 zurück, wenn zuvor der Standardwert aktiv war.

Wie bewahre ich nicht gemappte Glyphen (U+FFFD)?

Standardmäßig filtern die High-Level-Accessoren (extract_text / extract_words / extract_spans) Glyphen ohne Unicode-Mapping heraus (sie würden als U+FFFD erscheinen). Auf Seiten, deren sichtbare Glyphen alle auf U+FFFD abgebildet werden — z. B. bei mathematischen Zeichensätzen wie MSAM10 — kann das zu leerer Ausgabe führen. set_preserve_unmapped_glyphs(true) lässt diese Accessoren die Ersatzzeichen behalten, sodass Sie sie sehen und nachverarbeiten können. Die Funktion gibt den vorherigen Einstellungswert zurück.

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)

Auf C-ABI-Ebene nimmt pdf_oxide_set_preserve_unmapped_glyphs(preserve) den Wert 1 (beibehalten) oder 0 (filtern) und gibt den vorherigen Wert als 0 oder 1 zurück.

FAQ

Wo werden OCR-Modelle gespeichert? In $PDF_OXIDE_MODEL_DIR, falls gesetzt, andernfalls im Plattform-Cache (~/.cache/pdf_oxide/models unter Linux). Dieser Pfad ist auch das, was prefetch_models zurückgibt.

Ist es sicher, prefetch_models wiederholt aufzurufen? Ja — die Funktion ist idempotent. Bereits vorhandene Dateien werden übersprungen, weshalb ein Aufruf bei jedem Start als Sicherheitsnetz problemlos möglich ist.

Warum gibt prefetch_available false zurück, obwohl ich prefetch aufgerufen habe? Der Build wurde ohne das Feature ocr kompiliert, weshalb kein HTTP-Fetcher vorhanden ist. prefetch_models erstellt zwar das Cache-Verzeichnis, lädt aber nichts herunter — stellen Sie die Dateien manuell über model_manifest bereit.

Müssen die globalen Setter zurückgesetzt werden? Sie sind prozessglobal und bleiben wirksam, bis sie geändert werden. Stellen Sie deshalb den vorherigen Wert wieder her (den jeder Setter zurückgibt), wenn die Überschreibung nur für ein bestimmtes Dokument gelten soll. Beide Setter können nicht fehlschlagen und haben keinen Fehlerkanal.

Verwandte Seiten