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.
- 📄 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
- 🔒 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
| 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 |
- Go 1.24.4 or higher
- MuPDF library installed on your system
- GCC compiler (for CGo)
- Docker
- Docker Compose
-
Clone the repository
git clone git@github.com:tranlongan051020/golang-pdf.git
-
Build and run with Docker Compose
make docker-build make docker-run
-
Verify the service is running
make docker-test
-
View logs
make docker-logs
-
Stop the service
make docker-stop
-
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
-
Install Go dependencies
go mod download
-
Build the application
make build-mupdf # or make build -
Run the service
./pdf-service # or make run
The service will start on http://localhost:8080 by default.
Configuration is managed through config.yaml and can be overridden with environment variables prefixed with PDF_SERVICE_.
| 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 |
| max_file_size | 104857600 |
Max PDF file size (100MB) | |
| max_pages | 1000 |
Max pages per 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 |
export PDF_SERVICE_SERVER_PORT=9000
export PDF_SERVICE_LOGGING_LEVEL=info
export PDF_SERVICE_SECURITY_RATE_LIMIT_RPS=200GET /healthResponse:
OK
GET /health/detailedResponse:
{
"status": "healthy",
"version": "1.0.0",
"uptime": "2h30m15s",
"pool": {
"total": 10,
"available": 8,
"in_use": 2
}
}Extract text from a specific page of a PDF document.
Endpoint: POST /extract/text
Request:
Content-Type:multipart/form-datafile(required): PDF filepage(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..."
}Extract text from specific rectangular regions on PDF pages using bounding boxes.
Endpoint: POST /extract/coordinates
Request:
Content-Type:multipart/form-datafile(required): PDF filefields(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
bboxmust be array of exactly 4 floats- Page number must be >= 0
- Coordinates must be non-negative
Retrieve all form fields from a PDF document.
Endpoint: POST /form/fields
Request:
Content-Type:multipart/form-datafile(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 identifierValue(string): Current field valueType(int): Field type codeFlags(int): Field flags
Fill PDF form fields with provided values.
Endpoint: POST /form/fill
Request:
Content-Type:multipart/form-datafile(required): PDF filefields(required): JSON object with field names and valuesflatten(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.pdfResponse:
- Binary PDF file with filled form
Notes:
- When
flatten=true, form fields become non-editable - When
flatten=false, fields remain editable
Add custom text overlays to PDF pages at specified positions with advanced formatting options.
Endpoint: POST /add-text
Request:
Content-Type:multipart/form-datafile(required): PDF filetexts(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 addpoint(array, required):[x, y]coordinates using top-left originx: Distance from left edgey: 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
- Available:
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:falsechar_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.pdfExample - 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/pdfContent-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
Access Prometheus metrics for monitoring.
Endpoint: GET /metrics
Example:
curl http://localhost:9090/metricsAvailable Metrics:
- HTTP request duration
- Request count by endpoint
- Active requests
- PDF processing times
- Pool utilization
- Error rates
All endpoints return standardized error responses:
{
"error": "Error description"
}Common HTTP Status Codes:
400 Bad Request: Invalid input or validation error405 Method Not Allowed: Wrong HTTP method413 Payload Too Large: File exceeds max size429 Too Many Requests: Rate limit exceeded500 Internal Server Error: Server-side processing error
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.
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 ./....
├── 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
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 artifactsThe service uses several optimization techniques:
- Context Pooling: Pre-allocated MuPDF contexts reduce initialization overhead
- Rate Limiting: Prevents resource exhaustion under high load
- Timeouts: Request and processing timeouts prevent resource leaks
- Graceful Shutdown: Clean resource cleanup on termination
- Metrics: Real-time performance monitoring
Recommended Pool Size:
- Development: 5-10 contexts
- Production: 20-50 contexts (based on expected concurrency)
- 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
-
Check MuPDF installation:
pkg-config --modversion mupdf
-
Check port availability:
lsof -i :8080
-
Check logs:
# Docker make docker-logs # Local ./pdf-service
-
Verify PDF is valid:
file document.pdf
-
Check file size:
ls -lh document.pdf
-
Review logs for specific errors
- Reduce pool size in config.yaml
- Lower
max_file_sizelimit - Monitor metrics at
/metrics
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Write tests for new features
- Follow Go coding conventions
- Run
go fmtandgo vetbefore committing - Update documentation for API changes
- Add meaningful commit messages
For issues and questions:
- Issues: Create an issue
- Discussions: GitHub Discussions
- MuPDF - PDF processing engine
- Go community for excellent libraries and tools