pdf-rs ライブラリにおけるワークスペース構成とモジュール設計

マルチクレート構成の概要

pdf-rs プロジェクトは、単一のクレートではなく複数のクレートで構成される Cargo ワークスペースとして設計されています。このアーキテクチャにより、核心となる解析ロジック、派生マクロ、そして利用事例を明確に分離し、依存関係の管理を最適化しています。

ディレクトリ構成

プロジェクトルートにおける主要なディレクトリ構成は以下の通りです。

  • pdf/ : PDF 解析の中核機能を担うメインクレート
  • pdf_derive/ : 構造体派生を支援するプロセスマクロクレート
  • examples/ : ライブラリの_usage_を示すサンプル集
  • Cargo.toml : ワークスペース全体を定義する設定ファイル

ワークスペース設定の詳細

ルートディレクトリの Cargo.toml において、workspace メンバーを宣言することで、各サブクレートが统一管理されます。

[workspace]
members = [
    "pdf",          # 主要な解析エンジン
    "pdf_derive",   # コンパイル時マクロ
    "examples",     # 実行可能なデモ事例
]

各クレートの役割と構造

1. pdf クレート(核心モジュール)

このクレートは PDF ドキュメントの読み込み、オブジェクトの解釈、およびコンテンツの抽出を担当します。

ソースコード構成

  • src/lib.rs : 公開 API のエントリポイント
  • src/file/ : ドキュメント全体のハンドリング
  • src/object/ : PDF primitive オブジェクトの定義
  • src/content/ : ページ内の操作ストリーム解析
  • src/font/ : フォントマッピングとテキスト復元
  • src/error/ : 独自エラー型の定義

依存関係設定

必要に応じて機能を切り替えるため、optional 依存と feature フラグが活用されています。

[package]
name = "pdf"
version = "0.9.0"
edition = "2021"

[dependencies]
# 内部マクロへの参照
pdf_derive = { path = "../pdf_derive" }
# 構文解析コンビネータ
nom = "7.0"
# 文字コード変換
encoding = "0.2"
# 画像処理(機能フラグにより有効化)
image = { version = "0.24", optional = true }

[features]
default = ["async"]
# 非同期 I/O サポート
async = []
# 描画機能の有効化
render = ["image"]

2. pdf_derive クレート(マクロ支援)

Rust の型システムを用いて PDF オブジェクトへのマッピングを容易にするためのプロセスマクロを提供します。

[package]
name = "pdf_derive"
version = "0.9.0"
edition = "2021"

[lib]
proc-macro = true

[dependencies]
syn = "2.0"       # 構文木解析
quote = "1.0"     # コード生成
proc-macro2 = "1.0"

3. examples クレート(利用事例)

エンドユーザーがライブラリをどのように利用するかを示すバイナリ集です。GUI や CLI ツールの実装例が含まれます。

[package]
name = "pdf-examples"
version = "0.1.0"
edition = "2021"

[dependencies]
pdf = { path = "../pdf" }
# 機能フラグによる依存の最適化
egui = { version = "0.24", optional = true }
clap = { version = "4.0", optional = true }

[[example]]
name = "viewer"
required-features = ["egui"]

[[example]]
name = "extract"
required-features = ["clap"]

主要モジュールのインターフェース

file モジュール

PDF ドキュメント全体を表す構造体と、その初期化方法を提供します。

pub struct File {
    // 内部状態
}

impl File {
    // パスからドキュメントを開く
    pub fn load_from_path(source: impl AsRef<Path>) -> Result<File, PdfError> {
        // 実装
    }

    // バイト列から直接解析
    pub fn from_buffer(buffer: Vec<u8>) -> Result<File, PdfError> {
        // 実装
    }

    // ページへのアクセス
    pub fn get_pages(&self) -> Pages<'_> {
        // 実装
    }

    // 暗号化チェック
    pub fn has_encryption(&self) -> bool {
        // 実装
    }
}

object モジュール

PDF 仕様に基づくプリミティブオブジェクトの列挙型です。

pub enum Primitive {
    Null,
    Boolean(bool),
    Integer(i64),
    Real(f64),
    String(Vec<u8>),
    Name(Vec<u8>),
    Array(Vec<Primitive>),
    Dictionary(Dictionary),
    Stream(Stream),
    Reference(Reference),
}

content モジュール

ページコンテンツストリーム的操作を扱います。

pub struct ContentStream<'a> {
    // ページ内容
}

impl ContentStream<'_> {
    pub fn parse(&self) -> Result<DecodedStream, PdfError> {
        // 実装
    }
}

pub struct DecodedStream {
    // 解析済み内容
}

impl DecodedStream {
    pub fn iter_texts(&self) -> TextIterator<'_> {
        // テキスト要素のイテレータ
    }
}

開発とビルドのワークフロー

環境構築とコンパイル

リポジトリを取得後、ワークスペース全体を構築します。

# ソースコードの取得
git clone https://github.com/pdf-rs/pdf.git
cd pdf

# ワークスペース全体のビルド
cargo build --workspace

# 特定のサンプル実行
cargo run --example viewer

# 核心クレートのテスト
cargo test -p pdf

開発サイクル

各クレートごとに開発および検証を行います。

# 核心ライブラリのチェック
cd pdf
cargo check

# マクロクレートの検証
cd pdf_derive
cargo test

# 全体テストスイート
cargo test --workspace

リリース手順

依存関係の順序に注意して公開します。

# 最初にマクロクレートを公開
cargo publish -p pdf_derive

# 続いて核心ライブラリを公開
cargo publish -p pdf

機能フラグと依存関係

コアライブラリの機能

ユーザープロジェクトの Cargo.toml において、必要な機能を選択できます。

[dependencies]
pdf = { version = "0.9", features = ["async", "render"] }

# 利用可能なフラグ
# - "async": 非同期操作の有効化
# - "render": 画像レンダリング(image クレート依存)
# - "nom_parser": 構文解析バックエンド
# - "embedded_hal": 組み込み環境サポート

サンプル実行時のフラグ

サンプルプログラムを実行する際、対応する機能を有効化する必要があります。

cargo run --example viewer --features "egui"
cargo run --example extract --features "clap"

拡張と貢献の方針

新モジュールの追加

  1. pdf/src/ 配下に新規ディレクトリを作成
  2. pdf/src/lib.rs でモジュールを公開
  3. 必要に応じて Cargo.toml の依存関係を更新
  4. 該当モジュール用のテストケースを追加

サンプルの追加

  1. examples/src/bin/ に新規バイナリを作成
  2. examples/Cargo.toml[[example]] セクションを設定
  3. 必要な機能フラグを依存関係に明記

バージョン管理と互換性

ワークスペース内のバージョン同期

  • 主要クレート間はメジャーバージョンを揃える
  • 核心ライブラリとマクロクレートは同時リリースする
  • サンプルコードは主バージョンの変更に追従する

Semantic Versioning の適用

  • メジャー版本 : 破壊的な API 変更を含む
  • マイナー版本 : 後方互換性を保った機能追加
  • パッチ版本 : バグ修正のみで互換性を維持

コード配置のガイドライン

  • 業務ロジックは pdf/ クレートに実装
  • メタプログラミングは pdf_derive/ クレートに分離
  • 実装例は examples/ で提供しベストプラクティスを示す
  • テストコードは各クレート内の tests/ ディレクトリに配置

品質基準

  • Rust 標準のコーディングスタイルに従う
  • 公開 API には十分なドキュメントコメントを付与する
  • 新機能には対応するサンプルコードを添える
  • ワークスペース全体のビルド成功を維持する
構成の要約:
📦 pdf       - 主要 API を提供する解析エンジン
🔧 pdf_derive - 型派生を簡素化するマクロ群
📚 examples  - 実装パターンを示す事例集

タグ: rust pdf-parser cargo-workspace proc-macro system-architecture

5月29日 04:30 投稿