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.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +105 -0
  3. data/CLAUDE.md +173 -0
  4. data/Gemfile +2 -3
  5. data/README.adoc +456 -179
  6. data/Rakefile +100 -0
  7. data/TODO.roadmap/00-architecture.md +139 -0
  8. data/TODO.roadmap/01-autoload-migration.md +39 -0
  9. data/TODO.roadmap/02-isolate-dormant-code.md +51 -0
  10. data/TODO.roadmap/03-domain-model.md +66 -0
  11. data/TODO.roadmap/04-lexer.md +40 -0
  12. data/TODO.roadmap/05-parser.md +43 -0
  13. data/TODO.roadmap/06-graphics-state.md +45 -0
  14. data/TODO.roadmap/07-matrix-and-color.md +37 -0
  15. data/TODO.roadmap/08-svg-builder.md +51 -0
  16. data/TODO.roadmap/09-renderer.md +52 -0
  17. data/TODO.roadmap/10-visitor.md +58 -0
  18. data/TODO.roadmap/11-operator-coverage.md +103 -0
  19. data/TODO.roadmap/12-svg-domain.md +62 -0
  20. data/TODO.roadmap/13-translation-handlers.md +69 -0
  21. data/TODO.roadmap/14-ps-serializer.md +49 -0
  22. data/TODO.roadmap/15-cli-and-public-api.md +47 -0
  23. data/TODO.roadmap/16-specs.md +84 -0
  24. data/TODO.roadmap/17-docs-sync.md +47 -0
  25. data/TODO.roadmap/18-performance-and-determinism.md +47 -0
  26. data/TODO.roadmap/19-error-model.md +45 -0
  27. data/TODO.roadmap/20-font-and-text.md +46 -0
  28. data/TODO.roadmap/21-images.md +45 -0
  29. data/TODO.roadmap/22-forms-and-resources.md +25 -0
  30. data/TODO.roadmap/23-level2-level3.md +52 -0
  31. data/TODO.roadmap/24-ci-and-release.md +42 -0
  32. data/TODO.roadmap/README.md +77 -0
  33. data/docs/.gitignore +29 -0
  34. data/docs/CHANGELOG.md +114 -0
  35. data/docs/COMPLETE_DOCUMENTATION_STATUS.md +376 -0
  36. data/docs/DEPLOYMENT.md +456 -0
  37. data/docs/DEPLOYMENT_INSTRUCTIONS.md +229 -0
  38. data/docs/DOCUMENTATION_PLAN.md +425 -0
  39. data/docs/FINAL_SUMMARY.md +657 -0
  40. data/docs/Gemfile +15 -0
  41. data/docs/README.md +327 -0
  42. data/docs/_config.yml +99 -0
  43. data/docs/advanced-topics.adoc +370 -0
  44. data/docs/api-reference/colors.adoc +705 -0
  45. data/docs/api-reference/converter.adoc +699 -0
  46. data/docs/api-reference/execution-context.adoc +1210 -0
  47. data/docs/api-reference/graphics-state.adoc +1070 -0
  48. data/docs/api-reference/interpreter.adoc +810 -0
  49. data/docs/api-reference/matrix.adoc +1179 -0
  50. data/docs/api-reference/path-builder.adoc +1284 -0
  51. data/docs/api-reference/postsvg-module.adoc +388 -0
  52. data/docs/api-reference/svg-generator.adoc +891 -0
  53. data/docs/api-reference/tokenizer.adoc +925 -0
  54. data/docs/api-reference.adoc +221 -0
  55. data/docs/architecture/command-registry.adoc +1191 -0
  56. data/docs/architecture/conversion-pipeline.adoc +746 -0
  57. data/docs/architecture/design-decisions.adoc +999 -0
  58. data/docs/architecture/generator-stage.adoc +1115 -0
  59. data/docs/architecture/graphics-state-model.adoc +1089 -0
  60. data/docs/architecture/interpreter-stage.adoc +1125 -0
  61. data/docs/architecture/parser-stage.adoc +1051 -0
  62. data/docs/architecture.adoc +354 -0
  63. data/docs/cli-reference/batch-command.adoc +616 -0
  64. data/docs/cli-reference/check-command.adoc +677 -0
  65. data/docs/cli-reference/cli-options.adoc +802 -0
  66. data/docs/cli-reference/convert-command.adoc +462 -0
  67. data/docs/cli-reference/version-command.adoc +296 -0
  68. data/docs/cli-reference.adoc +317 -0
  69. data/docs/concepts/conversion-pipeline.adoc +903 -0
  70. data/docs/concepts/coordinate-systems.adoc +836 -0
  71. data/docs/concepts/graphics-state.adoc +861 -0
  72. data/docs/concepts/path-operations.adoc +1076 -0
  73. data/docs/concepts/postscript-language.adoc +859 -0
  74. data/docs/concepts/svg-generation.adoc +937 -0
  75. data/docs/concepts.adoc +198 -0
  76. data/docs/contributing.adoc +443 -0
  77. data/docs/development.adoc +420 -0
  78. data/docs/faq.adoc +493 -0
  79. data/docs/getting-started/basic-usage.adoc +538 -0
  80. data/docs/getting-started/common-workflows.adoc +577 -0
  81. data/docs/getting-started/first-conversion.adoc +492 -0
  82. data/docs/getting-started/installation.adoc +534 -0
  83. data/docs/getting-started.adoc +94 -0
  84. data/docs/index.adoc +248 -0
  85. data/docs/optimization.adoc +196 -0
  86. data/docs/ps2svg_compatibility.adoc +149 -0
  87. data/docs/quick-reference.adoc +453 -0
  88. data/docs/sitemap.adoc +337 -0
  89. data/docs/troubleshooting.adoc +486 -0
  90. data/docs/validation.adoc +772 -0
  91. data/exe/postsvg +1 -0
  92. data/lib/postsvg/cli.rb +104 -57
  93. data/lib/postsvg/color.rb +132 -0
  94. data/lib/postsvg/errors.rb +68 -3
  95. data/lib/postsvg/format_number.rb +22 -0
  96. data/lib/postsvg/graphics_context.rb +80 -0
  97. data/lib/postsvg/graphics_stack.rb +43 -0
  98. data/lib/postsvg/model/literals/array.rb +41 -0
  99. data/lib/postsvg/model/literals/dictionary.rb +34 -0
  100. data/lib/postsvg/model/literals/hex.rb +37 -0
  101. data/lib/postsvg/model/literals/name.rb +40 -0
  102. data/lib/postsvg/model/literals/number.rb +36 -0
  103. data/lib/postsvg/model/literals/procedure.rb +41 -0
  104. data/lib/postsvg/model/literals/string.rb +30 -0
  105. data/lib/postsvg/model/literals.rb +19 -0
  106. data/lib/postsvg/model/operator.rb +58 -0
  107. data/lib/postsvg/model/operators/arithmetic.rb +264 -0
  108. data/lib/postsvg/model/operators/boolean.rb +182 -0
  109. data/lib/postsvg/model/operators/color.rb +74 -0
  110. data/lib/postsvg/model/operators/container.rb +186 -0
  111. data/lib/postsvg/model/operators/control_flow.rb +119 -0
  112. data/lib/postsvg/model/operators/device.rb +21 -0
  113. data/lib/postsvg/model/operators/dictionary.rb +118 -0
  114. data/lib/postsvg/model/operators/font.rb +121 -0
  115. data/lib/postsvg/model/operators/graphics_state.rb +84 -0
  116. data/lib/postsvg/model/operators/painting.rb +29 -0
  117. data/lib/postsvg/model/operators/path.rb +169 -0
  118. data/lib/postsvg/model/operators/stack.rb +72 -0
  119. data/lib/postsvg/model/operators/transformations.rb +103 -0
  120. data/lib/postsvg/model/operators.rb +89 -0
  121. data/lib/postsvg/model/program.rb +68 -0
  122. data/lib/postsvg/model/token.rb +43 -0
  123. data/lib/postsvg/model.rb +17 -0
  124. data/lib/postsvg/options.rb +29 -0
  125. data/lib/postsvg/renderer.rb +85 -0
  126. data/lib/postsvg/serializer.rb +325 -0
  127. data/lib/postsvg/source/ast_builder.rb +308 -0
  128. data/lib/postsvg/source/lexer.rb +322 -0
  129. data/lib/postsvg/source/operand_stack.rb +55 -0
  130. data/lib/postsvg/source.rb +21 -0
  131. data/lib/postsvg/svg/attribute_parser.rb +45 -0
  132. data/lib/postsvg/svg/clip_path_registry.rb +44 -0
  133. data/lib/postsvg/svg/document.rb +22 -0
  134. data/lib/postsvg/svg/element.rb +84 -0
  135. data/lib/postsvg/svg/elements/circle.rb +36 -0
  136. data/lib/postsvg/svg/elements/clip_path.rb +26 -0
  137. data/lib/postsvg/svg/elements/defs.rb +24 -0
  138. data/lib/postsvg/svg/elements/ellipse.rb +38 -0
  139. data/lib/postsvg/svg/elements/group.rb +37 -0
  140. data/lib/postsvg/svg/elements/image.rb +35 -0
  141. data/lib/postsvg/svg/elements/line.rb +36 -0
  142. data/lib/postsvg/svg/elements/path.rb +32 -0
  143. data/lib/postsvg/svg/elements/polygon.rb +12 -0
  144. data/lib/postsvg/svg/elements/polyline.rb +32 -0
  145. data/lib/postsvg/svg/elements/rect.rb +44 -0
  146. data/lib/postsvg/svg/elements/svg.rb +39 -0
  147. data/lib/postsvg/svg/elements/text.rb +42 -0
  148. data/lib/postsvg/svg/elements.rb +31 -0
  149. data/lib/postsvg/svg/paint.rb +34 -0
  150. data/lib/postsvg/svg/parser.rb +39 -0
  151. data/lib/postsvg/svg/path_data/command.rb +27 -0
  152. data/lib/postsvg/svg/path_data/parser.rb +82 -0
  153. data/lib/postsvg/svg/path_data.rb +17 -0
  154. data/lib/postsvg/svg/stroke.rb +31 -0
  155. data/lib/postsvg/svg/transform_list.rb +59 -0
  156. data/lib/postsvg/svg.rb +22 -0
  157. data/lib/postsvg/svg_builder.rb +249 -0
  158. data/lib/postsvg/translation/arc_converter.rb +86 -0
  159. data/lib/postsvg/translation/bounding_box.rb +59 -0
  160. data/lib/postsvg/translation/context.rb +34 -0
  161. data/lib/postsvg/translation/handler_registry.rb +38 -0
  162. data/lib/postsvg/translation/handlers/circle_handler.rb +28 -0
  163. data/lib/postsvg/translation/handlers/clip_path_handler.rb +13 -0
  164. data/lib/postsvg/translation/handlers/defs_handler.rb +15 -0
  165. data/lib/postsvg/translation/handlers/ellipse_handler.rb +31 -0
  166. data/lib/postsvg/translation/handlers/group_handler.rb +21 -0
  167. data/lib/postsvg/translation/handlers/image_handler.rb +20 -0
  168. data/lib/postsvg/translation/handlers/line_handler.rb +23 -0
  169. data/lib/postsvg/translation/handlers/open_handler.rb +18 -0
  170. data/lib/postsvg/translation/handlers/path_handler.rb +356 -0
  171. data/lib/postsvg/translation/handlers/polygon_handler.rb +33 -0
  172. data/lib/postsvg/translation/handlers/polyline_handler.rb +31 -0
  173. data/lib/postsvg/translation/handlers/rect_handler.rb +27 -0
  174. data/lib/postsvg/translation/handlers/shared.rb +110 -0
  175. data/lib/postsvg/translation/handlers/svg_handler.rb +25 -0
  176. data/lib/postsvg/translation/handlers/text_handler.rb +56 -0
  177. data/lib/postsvg/translation/handlers.rb +25 -0
  178. data/lib/postsvg/translation/ps_renderer.rb +105 -0
  179. data/lib/postsvg/translation/record_emitter.rb +35 -0
  180. data/lib/postsvg/translation.rb +17 -0
  181. data/lib/postsvg/version.rb +1 -1
  182. data/lib/postsvg/visitors/ps_visitor/arithmetic.rb +125 -0
  183. data/lib/postsvg/visitors/ps_visitor/boolean.rb +105 -0
  184. data/lib/postsvg/visitors/ps_visitor/color.rb +53 -0
  185. data/lib/postsvg/visitors/ps_visitor/common.rb +66 -0
  186. data/lib/postsvg/visitors/ps_visitor/container.rb +164 -0
  187. data/lib/postsvg/visitors/ps_visitor/control_flow.rb +110 -0
  188. data/lib/postsvg/visitors/ps_visitor/device.rb +20 -0
  189. data/lib/postsvg/visitors/ps_visitor/dictionary.rb +89 -0
  190. data/lib/postsvg/visitors/ps_visitor/font.rb +93 -0
  191. data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +55 -0
  192. data/lib/postsvg/visitors/ps_visitor/painting.rb +90 -0
  193. data/lib/postsvg/visitors/ps_visitor/path.rb +112 -0
  194. data/lib/postsvg/visitors/ps_visitor/stack.rb +47 -0
  195. data/lib/postsvg/visitors/ps_visitor/transformations.rb +101 -0
  196. data/lib/postsvg/visitors/ps_visitor.rb +208 -0
  197. data/lib/postsvg/visitors.rb +9 -0
  198. data/lib/postsvg.rb +93 -59
  199. data/lychee.toml +86 -0
  200. metadata +216 -11
  201. 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]