Thank you for your interest in contributing! This document provides guidelines and information for contributors.
- Code of Conduct
- Getting Started
- Development Setup
- Project Structure
- Development Workflow
- Coding Standards
- Testing
- Documentation
- Submitting Changes
- License
This project adheres to the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior by opening an issue or contacting the maintainers.
- Rust: 1.70+ (Install Rust)
- Python: 3.8+ (for Python bindings)
- Git: For version control
- C Compiler: gcc or clang (for dependencies)
-
cargo-watch: Auto-reload on file changes
cargo install cargo-watch
-
cargo-tarpaulin: Code coverage
cargo install cargo-tarpaulin
-
maturin: Python packaging
pip install maturin # or uv tool install maturin
-
Fork and clone the repository:
git clone https://github.com/YOUR_USERNAME/pdf_oxide.git cd pdf_oxide -
Build the project:
cargo build
-
Run tests:
cargo test -
Set up pre-commit hooks (recommended):
./scripts/setup-hooks.sh
This installs a pre-commit hook that automatically runs:
- Code formatting (
cargo fmt --check) - Linting (
cargo clippy) - Build verification (
cargo check) - Library tests (
cargo test --lib) - Integration tests (
cargo test --tests) - Documentation tests (
cargo test --doc)
Alternative: You can use prek to pre-commit your codes every time you commit automatically..
# Install prek cargo binstall prek # or uv tool install maturin # or pip install prek # Install in your project prek install
- Code formatting (
See docs/planning/README.md for comprehensive documentation.
pdf_oxide/
├── src/ # Rust source code
├── tests/ # Integration tests
├── benches/ # Performance benchmarks
├── examples/ # Usage examples
├── python/ # Python bindings (PyO3)
├── docs/planning/ # Planning documents (16 files)
└── training/ # ML training scripts
- Check Issues
- Look for issues labeled
help-wantedorgood-first-issue - Comment on the issue to claim it
git checkout -b feature/your-feature-name
# or
git checkout -b fix/your-bug-fixBranch naming:
feature/- New featuresfix/- Bug fixesdocs/- Documentation updatestest/- Test additionsrefactor/- Code refactoring
Write code following our Coding Standards.
# Run all tests
cargo test
# Run specific test
cargo test test_name
# Run with features
cargo test --features ml
# Watch mode (auto-reload)
cargo watch -x test# Format code
cargo fmt
# Run linter
cargo clippy -- -D warnings
# Fix clippy suggestions
cargo clippy --fixFollow Conventional Commits:
git commit -m "feat: add PDF object parser"
git commit -m "fix: correct unicode mapping in ToUnicode CMap"
git commit -m "docs: update API documentation"Commit types:
feat: New featurefix: Bug fixdocs: Documentation onlytest: Adding testsrefactor: Code refactoringperf: Performance improvementchore: Maintenance tasks
git push origin feature/your-feature-nameThen create a pull request on GitHub.
- Follow Rust API Guidelines
- Use
rustfmt(configured inrustfmt.toml) - Maximum line length: 100 characters
- Use 4 spaces for indentation
// Modules and crates
mod pdf_parser;
// Types (structs, enums, traits)
struct PdfDocument;
enum Object;
trait Decoder;
// Functions and methods
fn parse_object() -> Result<Object>;
// Constants
const MAX_RECURSION_DEPTH: usize = 100;
// Static variables
static GLOBAL_CONFIG: Config = Config::new();// Use Result<T> for fallible operations
pub fn parse_pdf(path: &Path) -> Result<PdfDocument> {
// Use ? operator for error propagation
let file = File::open(path)?;
// Provide context when wrapping errors
let doc = parse_file(file)
.map_err(|e| Error::Parse(format!("Failed to parse {}: {}", path.display(), e)))?;
Ok(doc)
}
// Avoid unwrap() in library code (only in tests and examples)
// Use expect() with descriptive messages when appropriate/// Parse a PDF object from bytes.
///
/// # Arguments
///
/// * `bytes` - Raw bytes containing the PDF object
///
/// # Returns
///
/// Returns the parsed object or an error if parsing fails.
///
/// # Errors
///
/// Returns `Error::Parse` if the bytes don't represent a valid PDF object.
///
/// # Examples
///
/// ```
/// use pdf_oxide::parse_object;
///
/// let bytes = b"42";
/// let obj = parse_object(bytes)?;
/// assert_eq!(obj, Object::Integer(42));
/// ```
pub fn parse_object(bytes: &[u8]) -> Result<Object> {
// Implementation
}- Avoid
unsafeunless absolutely necessary - Document all
unsafeblocks with safety invariants - Prefer safe abstractions from the standard library
- Follow PEP 8
- Use
blackfor formatting - Type hints for all public functions
- Docstrings in Google style
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_integer() {
let obj = parse_object(b"42").unwrap();
assert_eq!(obj, Object::Integer(42));
}
#[test]
#[should_panic(expected = "Invalid")]
fn test_invalid_input() {
parse_object(b"invalid").unwrap();
}
}Located in tests/:
// tests/test_integration.rs
use pdf_oxide::PdfDocument;
#[test]
fn test_extract_text_from_simple_pdf() {
let mut doc = PdfDocument::open("tests/fixtures/simple.pdf").unwrap();
let text = doc.extract_text(0).unwrap();
assert!(text.contains("Hello, World!"));
}use proptest::prelude::*;
proptest! {
#[test]
fn test_roundtrip(s: String) {
let encoded = encode(&s);
let decoded = decode(&encoded)?;
prop_assert_eq!(s, decoded);
}
}- Library code: 80%+ coverage
- Critical paths: 100% coverage (parsing, error handling)
Check coverage:
cargo tarpaulin --out Html
open tarpaulin-report.html- All public items must have doc comments
- Include examples in doc comments
- Run
cargo docto check rendered docs
- Update relevant
PHASE_*.mdfiles if modifying implementation - Keep
README.mdup to date with new features - Document breaking changes
Add examples to examples/:
// examples/basic.rs
use pdf_oxide::PdfDocument;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let doc = PdfDocument::open("paper.pdf")?;
let text = doc.extract_text(0)?;
println!("{}", text);
Ok(())
}Before submitting a PR, ensure:
- Code compiles without warnings
- All tests pass (
cargo test) - Code is formatted (
cargo fmt) - Clippy passes (
cargo clippy -- -D warnings) - New code has tests
- Documentation is updated
- Commit messages follow conventions
- PR description explains changes clearly
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Related Issue
Fixes #(issue number)
## Testing
Describe testing done
## Checklist
- [ ] Tests pass
- [ ] Code formatted
- [ ] Documentation updated- Maintainers will review your PR
- Address feedback and push updates
- Once approved, your PR will be merged
- Your changes will appear in the next release
When working on performance-critical code:
-
Benchmark before and after:
cargo bench
-
Profile if needed:
cargo install flamegraph cargo flamegraph --bench my_bench
-
Consider memory usage:
- Avoid unnecessary allocations
- Use
Cow<str>when appropriate - Stream large files instead of loading entirely
When working on ML features (OCR, layout analysis):
- Document model requirements
- Provide ONNX conversion scripts
- Test on CPU-only systems
- Keep models small (<50MB)
- Document accuracy metrics
By contributing, you agree that your contributions will be dual licensed under MIT OR Apache-2.0, as defined in the Apache-2.0 license, without any additional terms or conditions.
This means:
- Your code will be available under permissive open-source licenses
- Users can choose either MIT or Apache-2.0 for their needs
- See LICENSE-MIT and LICENSE-APACHE for full terms
- Check
docs/spec/for PDF specification references - Read code comments and documentation
- Open an issue for questions
- Join discussions on GitHub Discussions
Contributors will be acknowledged in:
- GitHub contributors list
- Release notes
CONTRIBUTORS.mdfile (coming soon)
Thank you for contributing! 🎉