postsvg 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +105 -0
- data/CLAUDE.md +173 -0
- data/Gemfile +2 -3
- data/README.adoc +456 -179
- data/Rakefile +100 -0
- data/TODO.roadmap/00-architecture.md +139 -0
- data/TODO.roadmap/01-autoload-migration.md +39 -0
- data/TODO.roadmap/02-isolate-dormant-code.md +51 -0
- data/TODO.roadmap/03-domain-model.md +66 -0
- data/TODO.roadmap/04-lexer.md +40 -0
- data/TODO.roadmap/05-parser.md +43 -0
- data/TODO.roadmap/06-graphics-state.md +45 -0
- data/TODO.roadmap/07-matrix-and-color.md +37 -0
- data/TODO.roadmap/08-svg-builder.md +51 -0
- data/TODO.roadmap/09-renderer.md +52 -0
- data/TODO.roadmap/10-visitor.md +58 -0
- data/TODO.roadmap/11-operator-coverage.md +103 -0
- data/TODO.roadmap/12-svg-domain.md +62 -0
- data/TODO.roadmap/13-translation-handlers.md +69 -0
- data/TODO.roadmap/14-ps-serializer.md +49 -0
- data/TODO.roadmap/15-cli-and-public-api.md +47 -0
- data/TODO.roadmap/16-specs.md +84 -0
- data/TODO.roadmap/17-docs-sync.md +47 -0
- data/TODO.roadmap/18-performance-and-determinism.md +47 -0
- data/TODO.roadmap/19-error-model.md +45 -0
- data/TODO.roadmap/20-font-and-text.md +46 -0
- data/TODO.roadmap/21-images.md +45 -0
- data/TODO.roadmap/22-forms-and-resources.md +25 -0
- data/TODO.roadmap/23-level2-level3.md +52 -0
- data/TODO.roadmap/24-ci-and-release.md +42 -0
- data/TODO.roadmap/README.md +77 -0
- data/docs/.gitignore +29 -0
- data/docs/CHANGELOG.md +114 -0
- data/docs/COMPLETE_DOCUMENTATION_STATUS.md +376 -0
- data/docs/DEPLOYMENT.md +456 -0
- data/docs/DEPLOYMENT_INSTRUCTIONS.md +229 -0
- data/docs/DOCUMENTATION_PLAN.md +425 -0
- data/docs/FINAL_SUMMARY.md +657 -0
- data/docs/Gemfile +15 -0
- data/docs/README.md +327 -0
- data/docs/_config.yml +99 -0
- data/docs/advanced-topics.adoc +370 -0
- data/docs/api-reference/colors.adoc +705 -0
- data/docs/api-reference/converter.adoc +699 -0
- data/docs/api-reference/execution-context.adoc +1210 -0
- data/docs/api-reference/graphics-state.adoc +1070 -0
- data/docs/api-reference/interpreter.adoc +810 -0
- data/docs/api-reference/matrix.adoc +1179 -0
- data/docs/api-reference/path-builder.adoc +1284 -0
- data/docs/api-reference/postsvg-module.adoc +388 -0
- data/docs/api-reference/svg-generator.adoc +891 -0
- data/docs/api-reference/tokenizer.adoc +925 -0
- data/docs/api-reference.adoc +221 -0
- data/docs/architecture/command-registry.adoc +1191 -0
- data/docs/architecture/conversion-pipeline.adoc +746 -0
- data/docs/architecture/design-decisions.adoc +999 -0
- data/docs/architecture/generator-stage.adoc +1115 -0
- data/docs/architecture/graphics-state-model.adoc +1089 -0
- data/docs/architecture/interpreter-stage.adoc +1125 -0
- data/docs/architecture/parser-stage.adoc +1051 -0
- data/docs/architecture.adoc +354 -0
- data/docs/cli-reference/batch-command.adoc +616 -0
- data/docs/cli-reference/check-command.adoc +677 -0
- data/docs/cli-reference/cli-options.adoc +802 -0
- data/docs/cli-reference/convert-command.adoc +462 -0
- data/docs/cli-reference/version-command.adoc +296 -0
- data/docs/cli-reference.adoc +317 -0
- data/docs/concepts/conversion-pipeline.adoc +903 -0
- data/docs/concepts/coordinate-systems.adoc +836 -0
- data/docs/concepts/graphics-state.adoc +861 -0
- data/docs/concepts/path-operations.adoc +1076 -0
- data/docs/concepts/postscript-language.adoc +859 -0
- data/docs/concepts/svg-generation.adoc +937 -0
- data/docs/concepts.adoc +198 -0
- data/docs/contributing.adoc +443 -0
- data/docs/development.adoc +420 -0
- data/docs/faq.adoc +493 -0
- data/docs/getting-started/basic-usage.adoc +538 -0
- data/docs/getting-started/common-workflows.adoc +577 -0
- data/docs/getting-started/first-conversion.adoc +492 -0
- data/docs/getting-started/installation.adoc +534 -0
- data/docs/getting-started.adoc +94 -0
- data/docs/index.adoc +248 -0
- data/docs/optimization.adoc +196 -0
- data/docs/ps2svg_compatibility.adoc +149 -0
- data/docs/quick-reference.adoc +453 -0
- data/docs/sitemap.adoc +337 -0
- data/docs/troubleshooting.adoc +486 -0
- data/docs/validation.adoc +772 -0
- data/exe/postsvg +1 -0
- data/lib/postsvg/cli.rb +104 -57
- data/lib/postsvg/color.rb +132 -0
- data/lib/postsvg/errors.rb +68 -3
- data/lib/postsvg/format_number.rb +22 -0
- data/lib/postsvg/graphics_context.rb +80 -0
- data/lib/postsvg/graphics_stack.rb +43 -0
- data/lib/postsvg/model/literals/array.rb +41 -0
- data/lib/postsvg/model/literals/dictionary.rb +34 -0
- data/lib/postsvg/model/literals/hex.rb +37 -0
- data/lib/postsvg/model/literals/name.rb +40 -0
- data/lib/postsvg/model/literals/number.rb +36 -0
- data/lib/postsvg/model/literals/procedure.rb +41 -0
- data/lib/postsvg/model/literals/string.rb +30 -0
- data/lib/postsvg/model/literals.rb +19 -0
- data/lib/postsvg/model/operator.rb +58 -0
- data/lib/postsvg/model/operators/arithmetic.rb +264 -0
- data/lib/postsvg/model/operators/boolean.rb +182 -0
- data/lib/postsvg/model/operators/color.rb +74 -0
- data/lib/postsvg/model/operators/container.rb +186 -0
- data/lib/postsvg/model/operators/control_flow.rb +119 -0
- data/lib/postsvg/model/operators/device.rb +21 -0
- data/lib/postsvg/model/operators/dictionary.rb +118 -0
- data/lib/postsvg/model/operators/font.rb +121 -0
- data/lib/postsvg/model/operators/graphics_state.rb +84 -0
- data/lib/postsvg/model/operators/painting.rb +29 -0
- data/lib/postsvg/model/operators/path.rb +169 -0
- data/lib/postsvg/model/operators/stack.rb +72 -0
- data/lib/postsvg/model/operators/transformations.rb +103 -0
- data/lib/postsvg/model/operators.rb +89 -0
- data/lib/postsvg/model/program.rb +68 -0
- data/lib/postsvg/model/token.rb +43 -0
- data/lib/postsvg/model.rb +17 -0
- data/lib/postsvg/options.rb +29 -0
- data/lib/postsvg/renderer.rb +85 -0
- data/lib/postsvg/serializer.rb +325 -0
- data/lib/postsvg/source/ast_builder.rb +308 -0
- data/lib/postsvg/source/lexer.rb +322 -0
- data/lib/postsvg/source/operand_stack.rb +55 -0
- data/lib/postsvg/source.rb +21 -0
- data/lib/postsvg/svg/attribute_parser.rb +45 -0
- data/lib/postsvg/svg/clip_path_registry.rb +44 -0
- data/lib/postsvg/svg/document.rb +22 -0
- data/lib/postsvg/svg/element.rb +84 -0
- data/lib/postsvg/svg/elements/circle.rb +36 -0
- data/lib/postsvg/svg/elements/clip_path.rb +26 -0
- data/lib/postsvg/svg/elements/defs.rb +24 -0
- data/lib/postsvg/svg/elements/ellipse.rb +38 -0
- data/lib/postsvg/svg/elements/group.rb +37 -0
- data/lib/postsvg/svg/elements/image.rb +35 -0
- data/lib/postsvg/svg/elements/line.rb +36 -0
- data/lib/postsvg/svg/elements/path.rb +32 -0
- data/lib/postsvg/svg/elements/polygon.rb +12 -0
- data/lib/postsvg/svg/elements/polyline.rb +32 -0
- data/lib/postsvg/svg/elements/rect.rb +44 -0
- data/lib/postsvg/svg/elements/svg.rb +39 -0
- data/lib/postsvg/svg/elements/text.rb +42 -0
- data/lib/postsvg/svg/elements.rb +31 -0
- data/lib/postsvg/svg/paint.rb +34 -0
- data/lib/postsvg/svg/parser.rb +39 -0
- data/lib/postsvg/svg/path_data/command.rb +27 -0
- data/lib/postsvg/svg/path_data/parser.rb +82 -0
- data/lib/postsvg/svg/path_data.rb +17 -0
- data/lib/postsvg/svg/stroke.rb +31 -0
- data/lib/postsvg/svg/transform_list.rb +59 -0
- data/lib/postsvg/svg.rb +22 -0
- data/lib/postsvg/svg_builder.rb +249 -0
- data/lib/postsvg/translation/arc_converter.rb +86 -0
- data/lib/postsvg/translation/bounding_box.rb +59 -0
- data/lib/postsvg/translation/context.rb +34 -0
- data/lib/postsvg/translation/handler_registry.rb +38 -0
- data/lib/postsvg/translation/handlers/circle_handler.rb +28 -0
- data/lib/postsvg/translation/handlers/clip_path_handler.rb +13 -0
- data/lib/postsvg/translation/handlers/defs_handler.rb +15 -0
- data/lib/postsvg/translation/handlers/ellipse_handler.rb +31 -0
- data/lib/postsvg/translation/handlers/group_handler.rb +21 -0
- data/lib/postsvg/translation/handlers/image_handler.rb +20 -0
- data/lib/postsvg/translation/handlers/line_handler.rb +23 -0
- data/lib/postsvg/translation/handlers/open_handler.rb +18 -0
- data/lib/postsvg/translation/handlers/path_handler.rb +356 -0
- data/lib/postsvg/translation/handlers/polygon_handler.rb +33 -0
- data/lib/postsvg/translation/handlers/polyline_handler.rb +31 -0
- data/lib/postsvg/translation/handlers/rect_handler.rb +27 -0
- data/lib/postsvg/translation/handlers/shared.rb +110 -0
- data/lib/postsvg/translation/handlers/svg_handler.rb +25 -0
- data/lib/postsvg/translation/handlers/text_handler.rb +56 -0
- data/lib/postsvg/translation/handlers.rb +25 -0
- data/lib/postsvg/translation/ps_renderer.rb +105 -0
- data/lib/postsvg/translation/record_emitter.rb +35 -0
- data/lib/postsvg/translation.rb +17 -0
- data/lib/postsvg/version.rb +1 -1
- data/lib/postsvg/visitors/ps_visitor/arithmetic.rb +125 -0
- data/lib/postsvg/visitors/ps_visitor/boolean.rb +105 -0
- data/lib/postsvg/visitors/ps_visitor/color.rb +53 -0
- data/lib/postsvg/visitors/ps_visitor/common.rb +66 -0
- data/lib/postsvg/visitors/ps_visitor/container.rb +164 -0
- data/lib/postsvg/visitors/ps_visitor/control_flow.rb +110 -0
- data/lib/postsvg/visitors/ps_visitor/device.rb +20 -0
- data/lib/postsvg/visitors/ps_visitor/dictionary.rb +89 -0
- data/lib/postsvg/visitors/ps_visitor/font.rb +93 -0
- data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +55 -0
- data/lib/postsvg/visitors/ps_visitor/painting.rb +90 -0
- data/lib/postsvg/visitors/ps_visitor/path.rb +112 -0
- data/lib/postsvg/visitors/ps_visitor/stack.rb +47 -0
- data/lib/postsvg/visitors/ps_visitor/transformations.rb +101 -0
- data/lib/postsvg/visitors/ps_visitor.rb +208 -0
- data/lib/postsvg/visitors.rb +9 -0
- data/lib/postsvg.rb +93 -59
- data/lychee.toml +86 -0
- metadata +216 -11
- data/postsvg.gemspec +0 -38
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
= Converter Class
|
|
2
|
+
:page-nav_order: 2
|
|
3
|
+
:page-parent: API Reference
|
|
4
|
+
|
|
5
|
+
== Purpose
|
|
6
|
+
|
|
7
|
+
The [`Converter`](../../lib/postsvg/converter.rb:8) class provides the main interface for converting PostScript content to SVG with configurable options. It orchestrates the entire conversion pipeline from tokenization through SVG generation.
|
|
8
|
+
|
|
9
|
+
== References
|
|
10
|
+
|
|
11
|
+
* link:../index.adoc[Documentation Home]
|
|
12
|
+
* link:../api-reference.adoc[API Reference Overview]
|
|
13
|
+
* link:postsvg-module.adoc[Postsvg Module]
|
|
14
|
+
* link:interpreter.adoc[Interpreter Class]
|
|
15
|
+
* link:../getting-started/basic-usage.adoc[Basic Usage Guide]
|
|
16
|
+
|
|
17
|
+
== Concepts
|
|
18
|
+
|
|
19
|
+
**Converter**:: The main orchestrator class that coordinates tokenization, interpretation, and SVG generation.
|
|
20
|
+
|
|
21
|
+
**Strict Mode**:: A validation mode that fails immediately when encountering unknown or unsupported PostScript operators.
|
|
22
|
+
|
|
23
|
+
**BoundingBox**:: The rectangular area containing all graphics, extracted from the `%%BoundingBox` comment in PostScript files.
|
|
24
|
+
|
|
25
|
+
**Tokenization**:: The process of breaking PostScript source code into discrete tokens.
|
|
26
|
+
|
|
27
|
+
**Interpretation**:: The process of executing PostScript commands to build the graphics state.
|
|
28
|
+
|
|
29
|
+
== Class Overview
|
|
30
|
+
|
|
31
|
+
The [`Converter`](../../lib/postsvg/converter.rb:8) class is defined in [`lib/postsvg/converter.rb`](../../lib/postsvg/converter.rb:1).
|
|
32
|
+
|
|
33
|
+
**Responsibilities:**
|
|
34
|
+
* Extract BoundingBox from PostScript content
|
|
35
|
+
* Tokenize PostScript using [`Tokenizer`](../../lib/postsvg/tokenizer.rb:8)
|
|
36
|
+
* Create and configure [`Interpreter`](../../lib/postsvg/interpreter.rb:9)
|
|
37
|
+
* Coordinate conversion pipeline
|
|
38
|
+
* Return generated SVG
|
|
39
|
+
|
|
40
|
+
**Dependencies:**
|
|
41
|
+
* [`Tokenizer`](../../lib/postsvg/tokenizer.rb:8) - For tokenization
|
|
42
|
+
* [`Interpreter`](../../lib/postsvg/interpreter.rb:9) - For execution
|
|
43
|
+
|
|
44
|
+
## Class Methods
|
|
45
|
+
|
|
46
|
+
=== new
|
|
47
|
+
|
|
48
|
+
Create a new Converter instance.
|
|
49
|
+
|
|
50
|
+
**Syntax:**
|
|
51
|
+
|
|
52
|
+
[source,ruby]
|
|
53
|
+
----
|
|
54
|
+
converter = Postsvg::Converter.new(ps_content, strict_mode: false) <1>
|
|
55
|
+
----
|
|
56
|
+
<1> Initialize converter with PostScript content and options
|
|
57
|
+
|
|
58
|
+
**Where:**
|
|
59
|
+
|
|
60
|
+
`ps_content`:: String containing PostScript or EPS content
|
|
61
|
+
|
|
62
|
+
`strict_mode`:: (Optional) Boolean enabling strict validation
|
|
63
|
+
* `true` - Fail on unknown operators
|
|
64
|
+
* `false` - Ignore unknown operators (default)
|
|
65
|
+
|
|
66
|
+
**Returns:**
|
|
67
|
+
New `Converter` instance
|
|
68
|
+
|
|
69
|
+
**Source:**
|
|
70
|
+
[`lib/postsvg/converter.rb:12-15`](../../lib/postsvg/converter.rb:12)
|
|
71
|
+
|
|
72
|
+
.Create converter with default options
|
|
73
|
+
[example]
|
|
74
|
+
====
|
|
75
|
+
[source,ruby]
|
|
76
|
+
----
|
|
77
|
+
require 'postsvg'
|
|
78
|
+
|
|
79
|
+
ps_content = File.read('diagram.ps')
|
|
80
|
+
converter = Postsvg::Converter.new(ps_content)
|
|
81
|
+
|
|
82
|
+
# Converter is ready to convert
|
|
83
|
+
svg = converter.convert
|
|
84
|
+
----
|
|
85
|
+
====
|
|
86
|
+
|
|
87
|
+
.Create converter with strict mode
|
|
88
|
+
[example]
|
|
89
|
+
====
|
|
90
|
+
[source,ruby]
|
|
91
|
+
----
|
|
92
|
+
require 'postsvg'
|
|
93
|
+
|
|
94
|
+
ps_content = File.read('diagram.ps')
|
|
95
|
+
|
|
96
|
+
# Enable strict mode - fail on unknown operators
|
|
97
|
+
converter = Postsvg::Converter.new(
|
|
98
|
+
ps_content,
|
|
99
|
+
strict_mode: true
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
begin
|
|
103
|
+
svg = converter.convert
|
|
104
|
+
puts "Conversion successful"
|
|
105
|
+
rescue Postsvg::Error => e
|
|
106
|
+
puts "Error: #{e.message}"
|
|
107
|
+
# Handle unknown operators or other errors
|
|
108
|
+
end
|
|
109
|
+
----
|
|
110
|
+
|
|
111
|
+
Strict mode is useful for:
|
|
112
|
+
* Development and testing
|
|
113
|
+
* Ensuring complete operator support
|
|
114
|
+
* Validating PostScript compatibility
|
|
115
|
+
* Debugging conversion issues
|
|
116
|
+
====
|
|
117
|
+
|
|
118
|
+
== Instance Methods
|
|
119
|
+
|
|
120
|
+
=== convert
|
|
121
|
+
|
|
122
|
+
Perform the actual PostScript to SVG conversion.
|
|
123
|
+
|
|
124
|
+
**Syntax:**
|
|
125
|
+
|
|
126
|
+
[source,ruby]
|
|
127
|
+
----
|
|
128
|
+
svg_output = converter.convert <1>
|
|
129
|
+
----
|
|
130
|
+
<1> Execute conversion and return SVG
|
|
131
|
+
|
|
132
|
+
**Returns:**
|
|
133
|
+
String containing complete SVG document
|
|
134
|
+
|
|
135
|
+
**Raises:**
|
|
136
|
+
* [`Postsvg::ParseError`](../../lib/postsvg/errors.rb:1) - If PostScript syntax is invalid
|
|
137
|
+
* [`Postsvg::ConversionError`](../../lib/postsvg/errors.rb:1) - If conversion fails
|
|
138
|
+
* [`Postsvg::Error`](../../lib/postsvg/errors.rb:1) - For other Postsvg-specific errors
|
|
139
|
+
|
|
140
|
+
**Source:**
|
|
141
|
+
[`lib/postsvg/converter.rb:17-29`](../../lib/postsvg/converter.rb:17)
|
|
142
|
+
|
|
143
|
+
.Basic conversion
|
|
144
|
+
[example]
|
|
145
|
+
====
|
|
146
|
+
[source,ruby]
|
|
147
|
+
----
|
|
148
|
+
require 'postsvg'
|
|
149
|
+
|
|
150
|
+
ps_content = <<~PS
|
|
151
|
+
%!PS-Adobe-3.0 EPSF-3.0
|
|
152
|
+
%%BoundingBox: 0 0 200 200
|
|
153
|
+
newpath
|
|
154
|
+
100 100 moveto
|
|
155
|
+
150 100 lineto
|
|
156
|
+
stroke
|
|
157
|
+
PS
|
|
158
|
+
|
|
159
|
+
converter = Postsvg::Converter.new(ps_content)
|
|
160
|
+
svg = converter.convert
|
|
161
|
+
|
|
162
|
+
File.write('output.svg', svg)
|
|
163
|
+
puts "Conversion complete: #{svg.bytesize} bytes"
|
|
164
|
+
----
|
|
165
|
+
====
|
|
166
|
+
|
|
167
|
+
.Convert with error handling
|
|
168
|
+
[example]
|
|
169
|
+
====
|
|
170
|
+
[source,ruby]
|
|
171
|
+
----
|
|
172
|
+
require 'postsvg'
|
|
173
|
+
|
|
174
|
+
def safe_convert(ps_file, svg_file, strict: false)
|
|
175
|
+
ps_content = File.read(ps_file)
|
|
176
|
+
converter = Postsvg::Converter.new(ps_content, strict_mode: strict)
|
|
177
|
+
|
|
178
|
+
svg = converter.convert
|
|
179
|
+
File.write(svg_file, svg)
|
|
180
|
+
|
|
181
|
+
{
|
|
182
|
+
success: true,
|
|
183
|
+
output: svg_file,
|
|
184
|
+
size: svg.bytesize
|
|
185
|
+
}
|
|
186
|
+
rescue Postsvg::ParseError => e
|
|
187
|
+
{
|
|
188
|
+
success: false,
|
|
189
|
+
error: "Parse error: #{e.message}",
|
|
190
|
+
type: :parse_error
|
|
191
|
+
}
|
|
192
|
+
rescue Postsvg::ConversionError => e
|
|
193
|
+
{
|
|
194
|
+
success: false,
|
|
195
|
+
error: "Conversion error: #{e.message}",
|
|
196
|
+
type: :conversion_error
|
|
197
|
+
}
|
|
198
|
+
rescue StandardError => e
|
|
199
|
+
{
|
|
200
|
+
success: false,
|
|
201
|
+
error: "Unexpected error: #{e.message}",
|
|
202
|
+
type: :unknown_error
|
|
203
|
+
}
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
result = safe_convert('input.ps', 'output.svg', strict: true)
|
|
207
|
+
if result[:success]
|
|
208
|
+
puts "Success: #{result[:output]} (#{result[:size]} bytes)"
|
|
209
|
+
else
|
|
210
|
+
puts "Failed: #{result[:error]}"
|
|
211
|
+
end
|
|
212
|
+
----
|
|
213
|
+
====
|
|
214
|
+
|
|
215
|
+
**Implementation Details:**
|
|
216
|
+
|
|
217
|
+
The `convert` method:
|
|
218
|
+
1. Extracts BoundingBox using `extract_bounding_box` (private method)
|
|
219
|
+
2. Tokenizes PostScript using [`Tokenizer.tokenize`](../../lib/postsvg/tokenizer.rb:9)
|
|
220
|
+
3. Creates [`Interpreter`](../../lib/postsvg/interpreter.rb:9) with strict_mode setting
|
|
221
|
+
4. Calls `interpreter.interpret(tokens, bounding_box)`
|
|
222
|
+
5. Returns SVG from result hash: `result[:svg]`
|
|
223
|
+
|
|
224
|
+
== Attributes
|
|
225
|
+
|
|
226
|
+
=== ps_content (read-only)
|
|
227
|
+
|
|
228
|
+
Access the PostScript content stored in the converter.
|
|
229
|
+
|
|
230
|
+
**Syntax:**
|
|
231
|
+
|
|
232
|
+
[source,ruby]
|
|
233
|
+
----
|
|
234
|
+
content = converter.ps_content <1>
|
|
235
|
+
----
|
|
236
|
+
<1> Get the original PostScript content
|
|
237
|
+
|
|
238
|
+
**Returns:**
|
|
239
|
+
String containing the PostScript content passed to `new`
|
|
240
|
+
|
|
241
|
+
**Source:**
|
|
242
|
+
[`lib/postsvg/converter.rb:9`](../../lib/postsvg/converter.rb:9)
|
|
243
|
+
|
|
244
|
+
.Access PostScript content
|
|
245
|
+
[example]
|
|
246
|
+
====
|
|
247
|
+
[source,ruby]
|
|
248
|
+
----
|
|
249
|
+
converter = Postsvg::Converter.new(ps_content)
|
|
250
|
+
|
|
251
|
+
# Get content for inspection
|
|
252
|
+
content = converter.ps_content
|
|
253
|
+
puts "Content length: #{content.bytesize} bytes"
|
|
254
|
+
puts "Has BoundingBox: #{content.include?('%%BoundingBox')}"
|
|
255
|
+
----
|
|
256
|
+
====
|
|
257
|
+
|
|
258
|
+
=== strict_mode (read/write)
|
|
259
|
+
|
|
260
|
+
Get or set the strict mode flag.
|
|
261
|
+
|
|
262
|
+
**Syntax:**
|
|
263
|
+
|
|
264
|
+
[source,ruby]
|
|
265
|
+
----
|
|
266
|
+
is_strict = converter.strict_mode <1>
|
|
267
|
+
converter.strict_mode = true <2>
|
|
268
|
+
----
|
|
269
|
+
<1> Get current strict mode setting
|
|
270
|
+
<2> Set strict mode after initialization
|
|
271
|
+
|
|
272
|
+
**Returns:**
|
|
273
|
+
Boolean (`true` if strict mode enabled, `false` otherwise)
|
|
274
|
+
|
|
275
|
+
**Source:**
|
|
276
|
+
[`lib/postsvg/converter.rb:10`](../../lib/postsvg/converter.rb:10)
|
|
277
|
+
|
|
278
|
+
.Toggle strict mode
|
|
279
|
+
[example]
|
|
280
|
+
====
|
|
281
|
+
[source,ruby]
|
|
282
|
+
----
|
|
283
|
+
converter = Postsvg::Converter.new(ps_content)
|
|
284
|
+
|
|
285
|
+
# Check initial setting
|
|
286
|
+
puts "Strict mode: #{converter.strict_mode}" # false
|
|
287
|
+
|
|
288
|
+
# Try conversion with lenient mode first
|
|
289
|
+
begin
|
|
290
|
+
svg = converter.convert
|
|
291
|
+
puts "Lenient conversion succeeded"
|
|
292
|
+
rescue => e
|
|
293
|
+
puts "Even lenient mode failed: #{e.message}"
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Enable strict mode for another attempt
|
|
297
|
+
converter.strict_mode = true
|
|
298
|
+
|
|
299
|
+
begin
|
|
300
|
+
# This will fail on any unknown operators
|
|
301
|
+
svg = converter.convert
|
|
302
|
+
rescue Postsvg::Error => e
|
|
303
|
+
puts "Strict mode revealed: #{e.message}"
|
|
304
|
+
end
|
|
305
|
+
----
|
|
306
|
+
====
|
|
307
|
+
|
|
308
|
+
== Usage Patterns
|
|
309
|
+
|
|
310
|
+
=== Pattern 1: Simple Conversion
|
|
311
|
+
|
|
312
|
+
[source,ruby]
|
|
313
|
+
----
|
|
314
|
+
require 'postsvg'
|
|
315
|
+
|
|
316
|
+
# Read file
|
|
317
|
+
ps_content = File.read('input.ps')
|
|
318
|
+
|
|
319
|
+
# Create converter
|
|
320
|
+
converter = Postsvg::Converter.new(ps_content)
|
|
321
|
+
|
|
322
|
+
# Convert
|
|
323
|
+
svg = converter.convert
|
|
324
|
+
|
|
325
|
+
# Save
|
|
326
|
+
File.write('output.svg', svg)
|
|
327
|
+
----
|
|
328
|
+
|
|
329
|
+
=== Pattern 2: Strict Validation
|
|
330
|
+
|
|
331
|
+
[source,ruby]
|
|
332
|
+
----
|
|
333
|
+
require 'postsvg'
|
|
334
|
+
|
|
335
|
+
def validate_postscript_support(ps_file)
|
|
336
|
+
ps_content = File.read(ps_file)
|
|
337
|
+
|
|
338
|
+
# Use strict mode to check for unsupported operators
|
|
339
|
+
converter = Postsvg::Converter.new(ps_content, strict_mode: true)
|
|
340
|
+
converter.convert
|
|
341
|
+
|
|
342
|
+
{ supported: true, message: "All operators supported" }
|
|
343
|
+
rescue Postsvg::Error => e
|
|
344
|
+
{ supported: false, message: e.message }
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
result = validate_postscript_support('document.ps')
|
|
348
|
+
puts result[:supported] ? "✓ Fully supported" : "✗ #{result[:message]}"
|
|
349
|
+
----
|
|
350
|
+
|
|
351
|
+
=== Pattern 3: Fallback Strategy
|
|
352
|
+
|
|
353
|
+
[source,ruby]
|
|
354
|
+
----
|
|
355
|
+
require 'postsvg'
|
|
356
|
+
|
|
357
|
+
def convert_with_fallback(ps_file, svg_file)
|
|
358
|
+
ps_content = File.read(ps_file)
|
|
359
|
+
|
|
360
|
+
# Try strict mode first
|
|
361
|
+
begin
|
|
362
|
+
converter = Postsvg::Converter.new(ps_content, strict_mode: true)
|
|
363
|
+
svg = converter.convert
|
|
364
|
+
File.write(svg_file, svg)
|
|
365
|
+
return { status: :full_support, warnings: [] }
|
|
366
|
+
rescue Postsvg::Error => e
|
|
367
|
+
warnings = [e.message]
|
|
368
|
+
|
|
369
|
+
# Fall back to lenient mode
|
|
370
|
+
begin
|
|
371
|
+
converter = Postsvg::Converter.new(ps_content, strict_mode: false)
|
|
372
|
+
svg = converter.convert
|
|
373
|
+
File.write(svg_file, svg)
|
|
374
|
+
return { status: :partial_support, warnings: warnings }
|
|
375
|
+
rescue StandardError => e2
|
|
376
|
+
return { status: :failed, errors: [e.message, e2.message] }
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
result = convert_with_fallback('input.ps', 'output.svg')
|
|
382
|
+
puts "Status: #{result[:status]}"
|
|
383
|
+
puts "Warnings: #{result[:warnings]}" if result[:warnings]
|
|
384
|
+
----
|
|
385
|
+
|
|
386
|
+
=== Pattern 4: Batch Processing with Progress
|
|
387
|
+
|
|
388
|
+
[source,ruby]
|
|
389
|
+
----
|
|
390
|
+
require 'postsvg'
|
|
391
|
+
|
|
392
|
+
class BatchConverter
|
|
393
|
+
def initialize(files, strict: false)
|
|
394
|
+
@files = files
|
|
395
|
+
@strict = strict
|
|
396
|
+
@results = []
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def convert_all
|
|
400
|
+
@files.each_with_index do |file, index|
|
|
401
|
+
print "Converting #{index + 1}/#{@files.size}: #{File.basename(file)}... "
|
|
402
|
+
|
|
403
|
+
result = convert_single(file)
|
|
404
|
+
@results << result
|
|
405
|
+
|
|
406
|
+
puts result[:success] ? "✓" : "✗ #{result[:error]}"
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
print_summary
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
private
|
|
413
|
+
|
|
414
|
+
def convert_single(file)
|
|
415
|
+
ps_content = File.read(file)
|
|
416
|
+
converter = Postsvg::Converter.new(ps_content, strict_mode: @strict)
|
|
417
|
+
svg = converter.convert
|
|
418
|
+
|
|
419
|
+
output_file = file.sub(/\.(ps|eps)$/i, '.svg')
|
|
420
|
+
File.write(output_file, svg)
|
|
421
|
+
|
|
422
|
+
{ success: true, file: file, output: output_file }
|
|
423
|
+
rescue => e
|
|
424
|
+
{ success: false, file: file, error: e.message }
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def print_summary
|
|
428
|
+
successful = @results.count { |r| r[:success] }
|
|
429
|
+
failed = @results.count { |r| !r[:success] }
|
|
430
|
+
|
|
431
|
+
puts "\nSummary: #{successful} succeeded, #{failed} failed"
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# Usage
|
|
436
|
+
files = Dir.glob('*.ps')
|
|
437
|
+
BatchConverter.new(files, strict: false).convert_all
|
|
438
|
+
----
|
|
439
|
+
|
|
440
|
+
== Comparison with Module Methods
|
|
441
|
+
|
|
442
|
+
**Use [`Converter`](../../lib/postsvg/converter.rb:8) class when:**
|
|
443
|
+
- ✅ You need strict mode validation
|
|
444
|
+
- ✅ You want reusable converter instances
|
|
445
|
+
- ✅ You need access to intermediate state
|
|
446
|
+
- ✅ Building a service or library
|
|
447
|
+
- ✅ Require error handling control
|
|
448
|
+
|
|
449
|
+
**Use [`Postsvg.convert`](../../lib/postsvg.rb:13) when:**
|
|
450
|
+
- ✅ Quick one-off conversions
|
|
451
|
+
- ✅ Simplest possible API
|
|
452
|
+
- ✅ Default settings sufficient
|
|
453
|
+
- ✅ Scripting scenarios
|
|
454
|
+
- ✅ Minimal code desired
|
|
455
|
+
|
|
456
|
+
.Comparison example
|
|
457
|
+
[example]
|
|
458
|
+
====
|
|
459
|
+
[source,ruby]
|
|
460
|
+
----
|
|
461
|
+
# Module method (simpler)
|
|
462
|
+
svg = Postsvg.convert(ps_content)
|
|
463
|
+
|
|
464
|
+
# Converter class (more control)
|
|
465
|
+
converter = Postsvg::Converter.new(ps_content, strict_mode: true)
|
|
466
|
+
svg = converter.convert
|
|
467
|
+
----
|
|
468
|
+
|
|
469
|
+
Both produce the same SVG output, but the Converter class offers more control.
|
|
470
|
+
====
|
|
471
|
+
|
|
472
|
+
== Thread Safety
|
|
473
|
+
|
|
474
|
+
The `Converter` class is **not thread-safe**. Each thread should have its own instance:
|
|
475
|
+
|
|
476
|
+
.Correct multi-threaded usage
|
|
477
|
+
[example]
|
|
478
|
+
====
|
|
479
|
+
[source,ruby]
|
|
480
|
+
----
|
|
481
|
+
# Bad: Sharing converter across threads
|
|
482
|
+
converter = Postsvg::Converter.new(ps_content)
|
|
483
|
+
threads = 5.times.map do
|
|
484
|
+
Thread.new { converter.convert } # NOT THREAD-SAFE!
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# Good: Each thread has own converter
|
|
488
|
+
ps_files = Dir.glob('*.ps')
|
|
489
|
+
threads = ps_files.map do |file|
|
|
490
|
+
Thread.new do
|
|
491
|
+
content = File.read(file)
|
|
492
|
+
converter = Postsvg::Converter.new(content)
|
|
493
|
+
converter.convert
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
results = threads.map(&:value)
|
|
498
|
+
----
|
|
499
|
+
====
|
|
500
|
+
|
|
501
|
+
== Performance Characteristics
|
|
502
|
+
|
|
503
|
+
**Time Complexity:**
|
|
504
|
+
* Tokenization: O(n) where n = content length
|
|
505
|
+
* Interpretation: O(m) where m = number of operators
|
|
506
|
+
* SVG Generation: O(p) where p = number of paths
|
|
507
|
+
|
|
508
|
+
**Space Complexity:**
|
|
509
|
+
* Memory usage proportional to:
|
|
510
|
+
- Input file size
|
|
511
|
+
- Number of paths
|
|
512
|
+
- Graphics state stack depth
|
|
513
|
+
- Dictionary entries
|
|
514
|
+
|
|
515
|
+
**Typical Performance:**
|
|
516
|
+
* Small files (<100KB): <1 second
|
|
517
|
+
* Medium files (100KB-1MB): 1-5 seconds
|
|
518
|
+
* Large files (>1MB): 5-30 seconds
|
|
519
|
+
|
|
520
|
+
.Performance monitoring
|
|
521
|
+
[example]
|
|
522
|
+
====
|
|
523
|
+
[source,ruby]
|
|
524
|
+
----
|
|
525
|
+
require 'postsvg'
|
|
526
|
+
require 'benchmark'
|
|
527
|
+
|
|
528
|
+
ps_content = File.read('large_file.ps')
|
|
529
|
+
puts "File size: #{ps_content.bytesize / 1024} KB"
|
|
530
|
+
|
|
531
|
+
time = Benchmark.measure do
|
|
532
|
+
converter = Postsvg::Converter.new(ps_content)
|
|
533
|
+
@svg = converter.convert
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
puts "Conversion time: #{'%.2f' % time.real} seconds"
|
|
537
|
+
puts "Output size: #{@svg.bytesize / 1024} KB"
|
|
538
|
+
puts "Ratio: #{'%.1f' % (@svg.bytesize.to_f / ps_content.bytesize)}x"
|
|
539
|
+
----
|
|
540
|
+
====
|
|
541
|
+
|
|
542
|
+
== Error Handling
|
|
543
|
+
|
|
544
|
+
The `Converter` class can raise several types of errors:
|
|
545
|
+
|
|
546
|
+
**Error Hierarchy:**
|
|
547
|
+
|
|
548
|
+
[source]
|
|
549
|
+
----
|
|
550
|
+
StandardError
|
|
551
|
+
└─ Postsvg::Error
|
|
552
|
+
├─ Postsvg::ParseError
|
|
553
|
+
├─ Postsvg::ConversionError
|
|
554
|
+
└─ Postsvg::ValidationError
|
|
555
|
+
----
|
|
556
|
+
|
|
557
|
+
.Handle specific error types
|
|
558
|
+
[example]
|
|
559
|
+
====
|
|
560
|
+
[source,ruby]
|
|
561
|
+
----
|
|
562
|
+
require 'postsvg'
|
|
563
|
+
|
|
564
|
+
def convert_with_detailed_error_handling(ps_file)
|
|
565
|
+
ps_content = File.read(ps_file)
|
|
566
|
+
converter = Postsvg::Converter.new(ps_content, strict_mode: true)
|
|
567
|
+
|
|
568
|
+
svg = converter.convert
|
|
569
|
+
|
|
570
|
+
{
|
|
571
|
+
status: :success,
|
|
572
|
+
svg: svg,
|
|
573
|
+
file_size: svg.bytesize
|
|
574
|
+
}
|
|
575
|
+
rescue Postsvg::ParseError => e
|
|
576
|
+
{
|
|
577
|
+
status: :parse_error,
|
|
578
|
+
message: e.message,
|
|
579
|
+
recommendation: "Check PostScript syntax"
|
|
580
|
+
}
|
|
581
|
+
rescue Postsvg::ConversionError => e
|
|
582
|
+
{
|
|
583
|
+
status: :conversion_error,
|
|
584
|
+
message: e.message,
|
|
585
|
+
recommendation: "Try lenient mode or check operators"
|
|
586
|
+
}
|
|
587
|
+
rescue Postsvg::ValidationError => e
|
|
588
|
+
{
|
|
589
|
+
status: :validation_error,
|
|
590
|
+
message: e.message,
|
|
591
|
+
recommendation: "Validate file with 'postsvg check'"
|
|
592
|
+
}
|
|
593
|
+
rescue Postsvg::Error => e
|
|
594
|
+
{
|
|
595
|
+
status: :error,
|
|
596
|
+
message: e.message,
|
|
597
|
+
recommendation: "Review error message"
|
|
598
|
+
}
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
result = convert_with_detailed_error_handling('input.ps')
|
|
602
|
+
case result[:status]
|
|
603
|
+
when :success
|
|
604
|
+
puts "✓ Success: #{result[:file_size]} bytes"
|
|
605
|
+
when :parse_error, :conversion_error, :validation_error
|
|
606
|
+
puts "✗ #{result[:status]}: #{result[:message]}"
|
|
607
|
+
puts " Try: #{result[:recommendation]}"
|
|
608
|
+
else
|
|
609
|
+
puts "✗ Error: #{result[:message]}"
|
|
610
|
+
end
|
|
611
|
+
----
|
|
612
|
+
====
|
|
613
|
+
|
|
614
|
+
== Advanced Usage
|
|
615
|
+
|
|
616
|
+
=== Custom Error Recovery
|
|
617
|
+
|
|
618
|
+
[source,ruby]
|
|
619
|
+
----
|
|
620
|
+
require 'postsvg'
|
|
621
|
+
|
|
622
|
+
class RobustConverter
|
|
623
|
+
def initialize(ps_content)
|
|
624
|
+
@ps_content = ps_content
|
|
625
|
+
@attempts = []
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def convert
|
|
629
|
+
# Attempt 1: Strict mode
|
|
630
|
+
attempt_strict ||
|
|
631
|
+
# Attempt 2: Lenient mode
|
|
632
|
+
attempt_lenient ||
|
|
633
|
+
# Attempt 3: With cleanup
|
|
634
|
+
attempt_with_cleanup ||
|
|
635
|
+
# Final: Report failure
|
|
636
|
+
report_failure
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
private
|
|
640
|
+
|
|
641
|
+
def attempt_strict
|
|
642
|
+
converter = Postsvg::Converter.new(@ps_content, strict_mode: true)
|
|
643
|
+
svg = converter.convert
|
|
644
|
+
@attempts << { mode: :strict, success: true }
|
|
645
|
+
svg
|
|
646
|
+
rescue => e
|
|
647
|
+
@attempts << { mode: :strict, success: false, error: e.message }
|
|
648
|
+
nil
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
def attempt_lenient
|
|
652
|
+
converter = Postsvg::Converter.new(@ps_content, strict_mode: false)
|
|
653
|
+
svg = converter.convert
|
|
654
|
+
@attempts << { mode: :lenient, success: true }
|
|
655
|
+
svg
|
|
656
|
+
rescue => e
|
|
657
|
+
@attempts << { mode: :lenient, success: false, error: e.message }
|
|
658
|
+
nil
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
def attempt_with_cleanup
|
|
662
|
+
# Try cleaning common issues
|
|
663
|
+
cleaned = @ps_content.gsub(/%%\w+:.*$/, '') # Remove some comments
|
|
664
|
+
converter = Postsvg::Converter.new(cleaned, strict_mode: false)
|
|
665
|
+
svg = converter.convert
|
|
666
|
+
@attempts << { mode: :cleaned, success: true }
|
|
667
|
+
svg
|
|
668
|
+
rescue => e
|
|
669
|
+
@attempts << { mode: :cleaned, success: false, error: e.message }
|
|
670
|
+
nil
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
def report_failure
|
|
674
|
+
puts "All conversion attempts failed:"
|
|
675
|
+
@attempts.each_with_index do |attempt, i|
|
|
676
|
+
puts " #{i + 1}. #{attempt[:mode]}: #{attempt[:error]}"
|
|
677
|
+
end
|
|
678
|
+
raise Postsvg::ConversionError, "All conversion strategies failed"
|
|
679
|
+
end
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
# Usage
|
|
683
|
+
robust = RobustConverter.new(File.read('problematic.ps'))
|
|
684
|
+
svg = robust.convert
|
|
685
|
+
----
|
|
686
|
+
|
|
687
|
+
== Next Steps
|
|
688
|
+
|
|
689
|
+
* Learn about link:interpreter.adoc[Interpreter Class] for execution details
|
|
690
|
+
* Review link:execution-context.adoc[ExecutionContext] for state management
|
|
691
|
+
* See link:../getting-started/basic-usage.adoc[Basic Usage] for practical examples
|
|
692
|
+
* Check link:../advanced-topics/strict-mode.adoc[Strict Mode] for validation details
|
|
693
|
+
|
|
694
|
+
== Bibliography
|
|
695
|
+
|
|
696
|
+
* link:postsvg-module.adoc[Postsvg Module Documentation]
|
|
697
|
+
* link:interpreter.adoc[Interpreter Class Documentation]
|
|
698
|
+
* link:../architecture.adoc[Architecture Overview]
|
|
699
|
+
* link:../getting-started/basic-usage.adoc[Basic Usage Guide]
|