Skip to content

Repository files navigation

PDF Service API

Go Version License Docker

A high-performance, production-ready PDF processing service built with Go and MuPDF. This service provides comprehensive REST APIs for PDF text extraction, form manipulation, and text overlay operations with advanced features like request tracking, rate limiting, performance monitoring, and graceful shutdown.

✨ Features

Core Capabilities

  • 📄 PDF Text Extraction: Extract text from specific pages or by rectangular coordinates
  • 📝 Form Operations: Retrieve and fill PDF form fields with optional flattening
  • ✍️ Text Overlay: Add custom text to PDFs at specified positions with font customization
  • 🚀 High Performance: Context pooling and efficient resource management

Production Features

  • 🔒 Security: Input validation, sanitization, rate limiting, and request ID tracking
  • 📊 Monitoring: Built-in health checks and Prometheus metrics export
  • Reliability: Graceful shutdown, comprehensive error handling, and race-condition-free
  • 📝 Observability: Structured logging with Logrus and Zap
  • 🐳 Docker-Ready: Complete containerization support with Docker Compose

🛠 Technology Stack

Component Technology
Language Go 1.24.4+
PDF Engine MuPDF (via CGo)
HTTP Server Standard library net/http
Logging Logrus + Zap
Metrics Prometheus
Configuration Viper (YAML + Environment Variables)
Validation go-playground/validator/v10
Containerization Docker + Docker Compose

Prerequisites

For Local Development

  • Go 1.24.4 or higher
  • MuPDF library installed on your system
  • GCC compiler (for CGo)

For Docker Development

  • Docker
  • Docker Compose

Installation & Setup

Option 1: Running with Docker (Recommended)

  1. Clone the repository

    git clone git@github.com:tranlongan051020/golang-pdf.git
  2. Build and run with Docker Compose

    make docker-build
    make docker-run
  3. Verify the service is running

    make docker-test
  4. View logs

    make docker-logs
  5. Stop the service

    make docker-stop

Option 2: Running Locally

  1. Install MuPDF (if not already installed)

    # macOS
    brew install mupdf
    
    # Ubuntu/Debian
    sudo apt-get install libmupdf-dev
    
    # Fedora/RHEL
    sudo dnf install mupdf-devel
  2. Install Go dependencies

    go mod download
  3. Build the application

    make build-mupdf
    # or
    make build
  4. Run the service

    ./pdf-service
    # or
    make run

The service will start on http://localhost:8080 by default.

⚙️ Configuration

Configuration is managed through config.yaml and can be overridden with environment variables prefixed with PDF_SERVICE_.

Key Configuration Options

Section Key Default Description
server host 0.0.0.0 Server host binding
server port 8080 Server port
server read_timeout 30s HTTP read timeout
server write_timeout 30s HTTP write timeout
server idle_timeout 120s HTTP idle timeout
server max_header_bytes 1048576 Max HTTP header size (1MB)
server shutdown_timeout 30s Graceful shutdown timeout
pdf max_file_size 104857600 Max PDF file size (100MB)
pdf max_pages 1000 Max pages per PDF
pdf processing_timeout 5m PDF processing timeout
pool size 10 MuPDF context pool size
security rate_limit_rps 100 Rate limit (requests/sec)
security rate_limit_burst 200 Rate limit burst size
logging level info Log level (debug/info/warn/error)
logging format json Log format (text/json)
metrics enabled true Enable Prometheus metrics
metrics path /metrics Metrics endpoint path

Environment Variable Examples

export PDF_SERVICE_SERVER_PORT=9000
export PDF_SERVICE_LOGGING_LEVEL=info
export PDF_SERVICE_SECURITY_RATE_LIMIT_RPS=200

📚 API Documentation

Health Check

Simple Health Check

GET /health

Response:

OK

Detailed Health Check

GET /health/detailed

Response:

{
  "status": "healthy",
  "version": "1.0.0",
  "uptime": "2h30m15s",
  "pool": {
    "total": 10,
    "available": 8,
    "in_use": 2
  }
}

1. Extract Text from PDF

Extract text from a specific page of a PDF document.

Endpoint: POST /extract/text

Request:

  • Content-Type: multipart/form-data
  • file (required): PDF file
  • page (optional): Page number (0-based, default: 0)

Example:

curl -X POST http://localhost:8080/extract/text \
  -F "file=@document.pdf" \
  -F "page=0"

Response:

{
  "text": "Extracted text content from the PDF page..."
}

2. Extract Text by Coordinates

Extract text from specific rectangular regions on PDF pages using bounding boxes.

Endpoint: POST /extract/coordinates

Request:

  • Content-Type: multipart/form-data
  • file (required): PDF file
  • fields (required): JSON array of coordinate fields

Field Structure:

{
  "field_name": "field_identifier",
  "page": 0,
  "bbox": [x1, y1, x2, y2]
}

BBox Format: [x1, y1, x2, y2] where:

  • (x1, y1) = top-left corner coordinates
  • (x2, y2) = bottom-right corner coordinates
  • All values must be non-negative floats

Example:

curl -X POST http://localhost:8080/extract/coordinates \
  -F "file=@document.pdf" \
  -F 'fields=[
    {
      "field_name": "customer_name",
      "page": 0,
      "bbox": [100.0, 200.0, 400.0, 250.0]
    },
    {
      "field_name": "invoice_number",
      "page": 0,
      "bbox": [100.0, 300.0, 300.0, 330.0]
    }
  ]'

Response:

{
  "results": [
    {
      "field_name": "customer_name",
      "text": "John Doe",
      "success": true
    },
    {
      "field_name": "invoice_number",
      "text": "INV-12345",
      "success": true
    }
  ]
}

Error Handling: If extraction fails for a specific field:

{
  "field_name": "problematic_field",
  "text": "",
  "success": false,
  "error": "error description"
}

Validation:

  • Maximum 1000 fields per request
  • bbox must be array of exactly 4 floats
  • Page number must be >= 0
  • Coordinates must be non-negative

3. Get Form Fields

Retrieve all form fields from a PDF document.

Endpoint: POST /form/fields

Request:

  • Content-Type: multipart/form-data
  • file (required): PDF file

Example:

curl -X POST http://localhost:8080/form/fields \
  -F "file=@form.pdf"

Response:

{
  "fields": [
    {
      "Name": "firstName",
      "Value": "John",
      "Type": 1,
      "Flags": 0
    },
    {
      "Name": "lastName",
      "Value": "Doe",
      "Type": 1,
      "Flags": 0
    },
    {
      "Name": "email",
      "Value": "john@example.com",
      "Type": 1,
      "Flags": 0
    }
  ]
}

Field Object Properties:

  • Name (string): Field identifier
  • Value (string): Current field value
  • Type (int): Field type code
  • Flags (int): Field flags

4. Fill Form

Fill PDF form fields with provided values.

Endpoint: POST /form/fill

Request:

  • Content-Type: multipart/form-data
  • file (required): PDF file
  • fields (required): JSON object with field names and values
  • flatten (optional): Boolean to flatten the form (default: false)

Example:

curl -X POST http://localhost:8080/form/fill \
  -F "file=@form.pdf" \
  -F 'fields={
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane@example.com",
    "subscribe": "true"
  }' \
  -F "flatten=true" \
  --output filled_form.pdf

Response:

  • Binary PDF file with filled form

Notes:

  • When flatten=true, form fields become non-editable
  • When flatten=false, fields remain editable

5. Add Text to PDF

Add custom text overlays to PDF pages at specified positions with advanced formatting options.

Endpoint: POST /add-text

Request:

  • Content-Type: multipart/form-data
  • file (required): PDF file
  • texts (required): JSON array of text fields

Text Field Structure:

{
  "page": 0,
  "text": "Your text here",
  "point": [x, y],
  "size": 12.0,
  "font": "Helv",
  "color": [0.0, 0.0, 0.0],
  "bold": false,
  "italic": false,
  "underline": false,
  "char_spacing": 0.0,
  "horizontal_scale": 100.0,
  "text_rendering_mode": 0
}

Note: All formatting fields (bold, italic, underline, char_spacing, horizontal_scale, text_rendering_mode) are optional.

Field Descriptions:

  • page (int, required): Page number (0-based)
  • text (string, required): Text content to add
  • point (array, required): [x, y] coordinates using top-left origin
    • x: Distance from left edge
    • y: Distance from TOP edge (0 = top of page)
  • size (float, optional): Font size in points (default: 12.0, range: 1-200)
  • font (string, optional): Font name. If omitted, auto-detects from existing page fonts
    • Available: Helv, Times, Cour, Symbol, ZapfDingbats, Japan, Korea, China, CNS, MS Gothic
  • color (array, optional): RGB values as [r, g, b] (range: 0.0-1.0, default: [0, 0, 0] = black)
  • bold (boolean, optional): Apply bold style via font variant (e.g., Helvetica-Bold). Default: false
    • ⚠️ Note: CJK fonts may not support bold
  • italic (boolean, optional): Apply italic style via font variant (e.g., Times-Italic). Default: false
    • ⚠️ Note: CJK fonts may not support italic
  • underline (boolean, optional): Draw underline beneath text. Default: false
  • char_spacing (float, optional): Character spacing in points (range: -1000 to 1000)
    • If omitted, preserves existing character spacing from the document
    • If set to 0, explicitly sets character spacing to 0 (no extra spacing)
    • If set to other values, adjusts character spacing accordingly
    • Negative values bring characters closer together
  • horizontal_scale (float, optional): Horizontal scaling percentage (range: 0-1000, default: 100)
    • Controls text width without changing font size
    • 100 = normal width (default)
    • <100 = narrower text (e.g., 80 = 80% width, useful for fixing CJK spacing issues)
    • >100 = wider text (e.g., 120 = 120% width)
    • ⚠️ Use case: Set to 75-85 for CJK fonts (MS Gothic, Japan) to reduce character spacing
  • text_rendering_mode (int, optional): PDF text rendering mode (range: -1 to 7, default: -1/disabled)
    • -1 = Use PDF default (fill text, equivalent to 0)
    • 0 = Fill text (default PDF behavior)
    • 1 = Stroke text (outline only, makes text appear lighter/thinner)
    • 2 = Fill then stroke text
    • 3-7 = Other PDF text rendering modes (clipping, invisible, etc.)
    • ⚠️ Use case: Set to 1 for lighter text appearance

Example:

curl -X POST http://localhost:8080/add-text \
  -F "file=@document.pdf" \
  -F 'texts=[
    {
      "page": 0,
      "text": "CONFIDENTIAL",
      "point": [100.0, 50.0],
      "size": 24.0,
      "font": "Helv",
      "color": [1.0, 0.0, 0.0],
      "bold": true,
      "underline": true
    },
    {
      "page": 0,
      "text": "Page 1 of 1",
      "point": [50.0, 750.0],
      "size": 10.0,
      "italic": true,
      "color": [0.4, 0.4, 0.4]
    },
    {
      "page": 0,
      "text": "S P A C E D   T E X T",
      "point": [200.0, 400.0],
      "size": 14.0,
      "char_spacing": 3.0
    }
  ]' \
  --output modified.pdf

Example - CJK Font with Accurate Spacing:

# For CJK fonts (MS Gothic, Japan), use horizontal_scale to fix character spacing
curl -X POST http://localhost:8080/add-text \
  -F "file=@document.pdf" \
  -F 'texts=[
    {
      "page": 0,
      "text": "123",
      "point": [68.0, 155.0],
      "size": 10.0,
      "font": "MS Gothic",
      "bold": false,
      "char_spacing": 0,
      "horizontal_scale": 80
    }
  ]' \
  --output modified.pdf

# For lighter appearance, add text_rendering_mode: 1
# Recommended combinations for CJK fonts:
# - horizontal_scale: 75-85 (narrower text)
# - char_spacing: -0.5 to -1 (tighter spacing)
# - text_rendering_mode: 1 (lighter appearance)

Response:

  • Binary PDF file with added text
  • Content-Type: application/pdf
  • Content-Disposition: attachment; filename=modified.pdf

Font Variant Mapping:

Base Font Bold Italic Bold+Italic
Helv/Helvetica Helvetica-Bold Helvetica-Oblique Helvetica-BoldOblique
Times Times-Bold Times-Italic Times-BoldItalic
Cour/Courier Courier-Bold Courier-Oblique Courier-BoldOblique
Symbol, ZapfDingbats, CJK fonts Styles ignored Styles ignored Styles ignored

Validation:

  • Maximum 100 text fields per request
  • Text length: 1-10,000 characters
  • Font size: 1.0-1000.0
  • Font name: < 100 characters
  • Page number must be >= 0
  • Point array must have exactly 2 elements
  • Color array (if provided) must have exactly 3 elements
  • Character spacing: -1000 to 1000

Metrics

Access Prometheus metrics for monitoring.

Endpoint: GET /metrics

Example:

curl http://localhost:9090/metrics

Available Metrics:

  • HTTP request duration
  • Request count by endpoint
  • Active requests
  • PDF processing times
  • Pool utilization
  • Error rates

Error Responses

All endpoints return standardized error responses:

{
  "error": "Error description"
}

Common HTTP Status Codes:

  • 400 Bad Request: Invalid input or validation error
  • 405 Method Not Allowed: Wrong HTTP method
  • 413 Payload Too Large: File exceeds max size
  • 429 Too Many Requests: Rate limit exceeded
  • 500 Internal Server Error: Server-side processing error

Request Tracking

All requests are automatically assigned a unique Request ID for tracing:

Request Header:

X-Request-ID: 550e8400-e29b-41d4-a716-446655440000

Response Header:

X-Request-ID: 550e8400-e29b-41d4-a716-446655440000

Use Request IDs in logs to trace specific requests through the system.


🧪 Testing

Run the test suite:

# Run all tests
make test

# Run specific test
go test ./internal/api/...
go test ./pkg/pdfcore/...

# Run with coverage
go test -cover ./...

# Run with race detection
go test -race ./...

👨‍💻 Development

Project Structure

.
├── cmd/
│   └── server/          # Application entry point
├── internal/
│   ├── api/             # HTTP handlers and routing
│   └── service/         # Business logic
├── pkg/
│   ├── config/          # Configuration management
│   ├── constants/       # Application constants
│   ├── health/          # Health check implementation
│   ├── logger/          # Logging setup
│   ├── metrics/         # Prometheus metrics
│   ├── middleware/      # HTTP middlewares
│   ├── pdfcore/         # MuPDF wrapper and pool
│   ├── sanitization/    # Input sanitization
│   └── shutdown/        # Graceful shutdown
├── config.yaml          # Configuration file
├── docker-compose.yml   # Docker composition
├── Dockerfile           # Docker build
└── Makefile             # Build automation

Available Make Commands

make help           # Show available commands
make build          # Build locally
make build-mupdf    # Build with MuPDF
make run            # Run locally
make test           # Run tests
make docker-build   # Build Docker image
make docker-run     # Run in Docker
make docker-stop    # Stop Docker container
make docker-logs    # View Docker logs
make docker-test    # Test Docker service
make docker-clean   # Clean Docker artifacts
make clean          # Clean build artifacts

⚡ Performance

The service uses several optimization techniques:

  1. Context Pooling: Pre-allocated MuPDF contexts reduce initialization overhead
  2. Rate Limiting: Prevents resource exhaustion under high load
  3. Timeouts: Request and processing timeouts prevent resource leaks
  4. Graceful Shutdown: Clean resource cleanup on termination
  5. Metrics: Real-time performance monitoring

Recommended Pool Size:

  • Development: 5-10 contexts
  • Production: 20-50 contexts (based on expected concurrency)

🔒 Security Features

  • Input Validation: All inputs validated using struct tags and custom validators
  • Sanitization: Filenames, JSON, and form parameters sanitized to prevent injection attacks
  • File Size Limits: Configurable limits prevent memory exhaustion and DoS attacks
  • Rate Limiting: Token bucket algorithm protects against abuse
  • Request Tracing: Full request lifecycle tracking with unique Request IDs
  • Error Sanitization: Generic error messages prevent information leakage
  • Memory Safety: Fixed use-after-free vulnerabilities in PDF processing
  • Race Condition Prevention: Thread-safe context pool with proper synchronization
  • Content-Type Validation: Strict validation of multipart/form-data requests

🔧 Troubleshooting

Service Won't Start

  1. Check MuPDF installation:

    pkg-config --modversion mupdf
  2. Check port availability:

    lsof -i :8080
  3. Check logs:

    # Docker
    make docker-logs
    
    # Local
    ./pdf-service

PDF Processing Errors

  1. Verify PDF is valid:

    file document.pdf
  2. Check file size:

    ls -lh document.pdf
  3. Review logs for specific errors

High Memory Usage

  1. Reduce pool size in config.yaml
  2. Lower max_file_size limit
  3. Monitor metrics at /metrics

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🤝 Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Development Guidelines

  • Write tests for new features
  • Follow Go coding conventions
  • Run go fmt and go vet before committing
  • Update documentation for API changes
  • Add meaningful commit messages

📞 Support

For issues and questions:

🙏 Acknowledgments

  • MuPDF - PDF processing engine
  • Go community for excellent libraries and tools

About

PDF processing service built with Go and MuPDF

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages