Purpose
Postsvg is a pure-Ruby PostScript (PS) and Encapsulated PostScript (EPS) transformer. It supports both directions: PS → SVG and SVG → PS (and SVG → EPS). No external tools, no Ghostscript, no Inkscape — the entire pipeline runs in Ruby.
Postsvg uses Nokogiri for the SVG parse side and a hand-written
comment-aware lexer / stack-machine interpreter for the PS source side.
Output is typed: the same Postsvg::Model::Program is produced whether
the source was parsed from PS or built from SVG, so round-trips are
trivial.
This library is particularly useful for applications that need to:
-
Convert legacy PostScript or SVG to the other format in pure Ruby
-
Process vector graphics without external dependencies
-
Generate PostScript from SVG programmatically (publishing pipelines)
-
Work with EPS files in pure Ruby environments
Features
-
PS/EPS → SVG conversion — Ruby API for converting PS or EPS source to SVG
-
SVG → PS/EPS conversion — Ruby API for the reverse direction
-
Command-line interface — Thor-based CLI for file and batch conversions in either direction
-
Pure-Ruby architecture — No external dependencies for PS/EPS, only Nokogiri for SVG
-
SVG optimization — Automatic deduplication of clipPath elements for efficient output
-
Round-trip preservation —
Postsvg.to_svg(Postsvg.to_ps(svg))for supported SVG elements preserves geometry -
OCP extensible — new operators or SVG elements are plug-ins, not edits to existing dispatch
Architecture
Postsvg is layered as a typeless surface → a typed model → an
emitter/serializer. The same Postsvg::Model::Program is produced by
the forward-direction parser and the reverse-direction translator, so
the same serializer can write it out in either direction.
Forward direction (PS/EPS → SVG)
PS / EPS source
│
▼
Postsvg::Source::Lexer (state-machine, comment-aware)
│ tokens
▼
Postsvg::Source::AstBuilder (tokens → Model::Program)
│
▼
Postsvg::Model::Program (typed records: Moveto, Stroke, Gsave, …)
│
▼
Postsvg::Renderer ┐
Postsvg::Visitors::PsVisitor ├── dispatches records, drives
Postsvg::SvgBuilder ┘ the SvgBuilder
│
▼
SVG string
Reverse direction (SVG → PS/EPS)
SVG string
│
▼
Postsvg::Svg::Parser (Nokogiri-backed)
│ Svg::Document with Element value objects
▼
Postsvg::Translation::PsRenderer
Postsvg::Translation::HandlerRegistry
Postsvg::Translation::Handlers::* (one per SVG element type)
│
▼
Postsvg::Model::Program (same model as forward direction)
│
▼
Postsvg::Serializer ─▶ PS / EPS source
MECE responsibility split
| Namespace | Owns |
|------------------------------------|-----------------------------------|
| Postsvg::Source | Lexer, AstBuilder, OperandStack |
| Postsvg::Model | Typed records, value equality |
| Postsvg::GraphicsContext/Stack| Immutable graphics-state snapshots|
| Postsvg::Matrix/Color/FormatNumber | Foundational value types |
| Postsvg::SvgBuilder | Append-only SVG emission |
| Postsvg::Renderer | PS → SVG orchestrator |
| Postsvg::Visitors | Per-operator dispatch |
| Postsvg::Svg | SVG domain model |
| Postsvg::Translation | SVG → PS dispatch + context |
| Postsvg::Serializer | Model → PS source |
| Postsvg::CLI | Argument parsing, I/O |
Installation
Add this line to your application’s Gemfile:
gem "postsvg"
And then execute:
bundle install
Or install it yourself as:
gem install postsvg
Conversion APIs
Postsvg provides four directional APIs covering both PS/EPS → SVG and SVG → PS/EPS. All conversions are pure-Ruby with no external process invocation.
Forward direction (PS/EPS → SVG)
| Method | Description |
|---|---|
|
Convert PS/EPS source string to SVG. |
|
Read PS/EPS file, optionally write SVG. |
|
Backwards-compatible aliases for |
This converts a PostScript square with gray fill to SVG format and saves it to a file.
require "postsvg"
# Convert and save in one step
Postsvg.to_svg_file("input.eps", "output.svg")
# Or get SVG content without saving
svg_content = Postsvg.to_svg_file("input.ps")
puts svg_content
Reverse direction (SVG → PS / EPS)
| Method | Description |
|---|---|
|
Convert SVG to PostScript source. |
|
Convert SVG to Encapsulated PostScript (EPSF-3.0 header). |
|
Read SVG file, optionally write PS. |
The SVG→PS pipeline supports <svg>, <g>, <path> (full M/L/H/V/C/S/Q/T/A/Z
command set), <rect>, <circle>, <ellipse>, <line>, <polyline>,
<polygon>, <text> (emits findfont/scalefont/setfont/show),
<image> (stubbed), <defs>, and <clipPath>. Arc paths use the W3C
endpoint-to-center conversion algorithm (SVG 1.1 §F.6.5) to emit the
equivalent PS arc operator.
Round-trip
The same Model::Program value object sits behind both directions, so
round-trip is a function composition:
Command-line interface
General
Postsvg provides a Thor-based command-line interface covering both directions (PS/EPS → SVG and SVG → PS/EPS), plus batch processing and version information.
The CLI is available through the postsvg executable installed with the
gem.
Converting PS/EPS to SVG
Syntax:
postsvg convert INPUT_FILE [OUTPUT_FILE]
postsvg to-svg INPUT_FILE [OUTPUT_FILE] # explicit alias
INPUT_FILE is a PostScript (.ps) or EPS (.eps) file. If OUTPUT_FILE
is omitted, SVG is written to stdout.
# Convert to stdout
postsvg convert input.ps
# Convert and save to file
postsvg to-svg input.eps output.svg
# Redirect stdout to file
postsvg convert input.ps > output.svg
Converting SVG to PS/EPS
Syntax:
postsvg to-ps INPUT.svg [OUTPUT.ps] # SVG → PostScript
postsvg to-eps INPUT.svg [OUTPUT.eps] # SVG → Encapsulated PostScript
If OUTPUT is omitted, PS/EPS source is written to stdout.
# Save to file
postsvg to-ps input.svg output.ps
# Pipe through stdout
postsvg to-eps input.svg > output.eps
Batch conversion
Syntax:
postsvg batch INPUT_DIR [OUTPUT_DIR]
batch auto-detects the direction by file extension:
-
.ps/.eps→.svg -
.svg→.ps
If OUTPUT_DIR is omitted, output files land in INPUT_DIR with the
converted extension.
# Convert all PS/EPS/SVG files in a directory
postsvg batch mixed_files/
# Convert to a different directory
postsvg batch ps_files/ svg_files/
Displaying version information
Syntax:
postsvg version <b class="conum">(1)</b>
-
Display the Postsvg version number
postsvg version
Output:
postsvg version 0.1.0
File validation
General
Postsvg includes a comprehensive validation tool for checking PostScript and EPS files. The validator performs syntax checking, semantic validation, and optional full conversion testing to ensure files are well-formed and can be successfully converted.
The validator is particularly useful for:
-
Verifying PostScript/EPS file integrity before conversion
-
Identifying syntax errors and structural issues
-
Validating compliance with PostScript standards
-
Integrating quality checks into CI/CD pipelines
Basic validation
Syntax:
postsvg check FILE... <b class="conum">(1)</b>
-
Validate one or more PostScript or EPS files
Where,
FILE-
Path to PostScript (.ps) or EPS (.eps) file(s) to validate. Multiple files can be specified.
- Returns
-
Exit code 0 if all files are valid, 1 if any file has errors.
# Validate a single file
postsvg check document.ps
# Validate multiple files
postsvg check file1.ps file2.eps file3.ps
# Validate all PS files in directory
postsvg check *.ps
The validator performs semantic-level validation by default, checking syntax, stack balance, and graphics state management.
Validation levels
Syntax:
postsvg check --level=LEVEL FILE... <b class="conum">(1)</b>
-
Validate with specified level: syntax, semantic, or full
Where,
LEVEL-
One of:
-
syntax- Fast syntax-only validation (header, delimiters, tokenization) -
semantic- Default level (syntax + stack/state checking) -
full- Strictest validation (semantic + complete conversion test)
-
# Fast syntax-only check
postsvg check --level=syntax document.ps
# Default semantic validation
postsvg check --level=semantic document.ps
# Full validation with conversion test
postsvg check --level=full document.ps
Syntax validation is fastest but only checks basic file structure. Semantic validation adds stack and state checking. Full validation attempts complete conversion and is most thorough.
Output formats
Syntax:
postsvg check --format=FORMAT FILE... <b class="conum">(1)</b>
-
Output validation results in specified format
Where,
FORMAT-
One of:
-
text- Human-readable colored console output (default) -
yaml- YAML structured format -
json- JSON structured format
-
# Default text output with colors
postsvg check document.ps
# YAML output
postsvg check --format=yaml document.ps
# JSON output for programmatic parsing
postsvg check --format=json document.ps
# JSON output without colors (for CI)
postsvg check --format=json --no-color document.ps
Text format provides human-readable output with color-coded status indicators. YAML and JSON formats are useful for programmatic processing or integration with other tools.
Validation options
The validator supports additional options for controlling behavior and output:
--verbose-
Show warnings and info messages in addition to errors
--quiet-
Suppress output for valid files (only show errors)
--no-color-
Disable colored output
--fail-fast-
Stop validation at first error
--eps-version=VERSION-
Validate EPS version (e.g.,
3.0)
# Verbose output with all details
postsvg check --verbose document.eps
# Quiet mode - only show errors
postsvg check --quiet *.ps
# Stop at first error
postsvg check --fail-fast file1.ps file2.ps file3.ps
# Validate specific EPS version
postsvg check --eps-version=3.0 diagram.eps
SVG optimization
General
Postsvg automatically optimizes SVG output to produce smaller, more efficient files while maintaining visual fidelity to the original PostScript graphics.
ClipPath deduplication
PostScript files often contain repeated clipping operations with identical paths. Postsvg automatically detects and deduplicates these clipPath definitions, significantly reducing SVG file size for documents with repeated clipping operations.
<defs>
<clipPath id="clipPath2">
<path d="M 0 0 L 100 100"/>
</clipPath>
<clipPath id="clipPath3">
<path d="M 0 0 L 100 100"/>
</clipPath>
<clipPath id="clipPath4">
<path d="M 0 0 L 100 100"/>
</clipPath>
</defs>
Postsvg generates a single definition and reuses it:
<defs>
<clipPath id="clipPath2">
<path d="M 0 0 L 100 100"/>
</clipPath>
</defs>
<path clip-path="url(#clipPath2)" .../>
<path clip-path="url(#clipPath2)" .../>
<path clip-path="url(#clipPath2)" .../>
This optimization is automatic and requires no configuration.
Test coverage
General
Postsvg maintains comprehensive test coverage to ensure reliability and correctness of PostScript to SVG conversion.
Core test suite
The core test suite consists of 90+ tests covering execution context and integration testing:
-
Execution context tests (84 tests) - Unit tests for stack operations, graphics state management, dictionary operations, path operations, helper methods, SVG generation, and clipPath deduplication
-
Integration tests (6 tests) - End-to-end tests using real PostScript files from ps2svg and vectory test suites
All core tests maintain 100% pass rate, ensuring critical functionality remains stable.
Running tests
Run the full test suite:
bundle exec rspec
Run only core tests:
bundle exec rspec spec/postsvg/execution_context_spec.rb spec/postsvg/integration_spec.rb
Run with documentation format:
bundle exec rspec --format documentation
PostScript language support
General
Postsvg provides comprehensive PostScript language support with detailed documentation covering fundamentals, operators, and conversion strategies.
The implementation supports common PostScript operations including path construction, painting, color management, graphics state, and coordinate transformations.
PostScript documentation
Complete PostScript language documentation is available at docs/POSTSCRIPT.adoc, organized into the following topics:
-
Fundamentals - PostScript language basics, syntax, and data types
-
Graphics model - Coordinate systems, paths, and painting model
-
Operators reference - Detailed documentation for all supported operators:
-
Path construction - moveto, lineto, curveto, closepath, newpath
-
Painting - stroke, fill
-
Graphics state - gsave, grestore, setlinewidth
-
-
translate, scale, rotate
-
-
Stack manipulation - dup, pop, exch, roll
-
Arithmetic - add, sub, mul, div
-
Control flow - if, ifelse, for, repeat
-
Dictionary - def, dict, begin, end
-
-
SVG mapping guide - How PostScript operations map to SVG
-
Implementation notes - Postsvg-specific details and design decisions
Supported PostScript operations
Path construction
-
moveto- Begin new subpath at coordinates -
lineto- Append straight line segment -
rlineto- Append relative line segment -
curveto- Append cubic Bézier curve -
closepath- Close current subpath -
newpath- Initialize new path
Painting
-
stroke- Stroke current path with current color and line width -
fill- Fill current path with current color
Color
-
setrgbcolor- Set RGB color (0-1 range for each component) -
setgray- Set grayscale color (0-1 range)
Graphics state
-
gsave- Save current graphics state to stack -
grestore- Restore graphics state from stack -
setlinewidth- Set line width for stroke operations
Transformations
-
translate- Translate coordinate system -
scale- Scale coordinate system -
rotate- Rotate coordinate system (angle in degrees)
Limitations
-
Text rendering is stubbed:
showmaps to SVG<text>but no font metrics. Convert text to outlines for fidelity. -
Real raster images (
image,imagemask): emit SVG<image>with a placeholder when decoded; PNG round-trip is planned but not yet wired. -
Real Level 3 shading (types 4–7) and forms are stubbed.
-
User-defined procedures invoked at runtime: parsed via the AST, but only first-order ones execute.
-
Procedures inside arrays (
[…]) keep literals only; operators inside[…]are an unusual construct in EPS drawing programs and not handled. -
Document-level DSC comments other than
%%BoundingBoxand%%HiResBoundingBoxare captured but not re-emitted.
For these gaps, see TODO.roadmap for the planned work.
Acknowledgments
This project uses test fixtures from ps2svg by Emmett Lalish, licensed under the MIT License. These test files help ensure the correctness of our SVG generation against a reference implementation. We are grateful for this valuable resource that helps validate PostScript to SVG conversion.
Development
Running tests
bundle exec rspec
Code style
bundle exec rubocop
Running all checks
bundle exec rake
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/metanorma/postsvg.
Copyright
Copyright Ribose.
License
The gem is available as open source under the terms of the BSD 2-Clause License.