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,810 @@
1
+ = Interpreter Class
2
+ :page-nav_order: 3
3
+ :page-parent: API Reference
4
+
5
+ == Purpose
6
+
7
+ The [`Interpreter`](../../lib/postsvg/interpreter.rb:9) class is the core execution engine that processes PostScript tokens and generates SVG output. It implements a stack-based interpreter using the Command Pattern to execute PostScript operators.
8
+
9
+ == References
10
+
11
+ * link:../index.adoc[Documentation Home]
12
+ * link:../api-reference.adoc[API Reference Overview]
13
+ * link:converter.adoc[Converter Class]
14
+ * link:execution-context.adoc[ExecutionContext Class]
15
+ * link:../architecture.adoc[Architecture Overview]
16
+
17
+ == Concepts
18
+
19
+ **Stack-Based Interpreter**:: An execution model where operands are pushed onto a stack and operators pop operands, perform operations, and push results.
20
+
21
+ **Command Pattern**:: A design pattern where operations are encapsulated as command objects that can be executed independently.
22
+
23
+ **Token Stream**:: A sequence of parsed PostScript tokens (numbers, strings, operators, etc.) ready for interpretation.
24
+
25
+ **Execution Context**:: The runtime state including operand stack, dictionary stack, and graphics state.
26
+
27
+ **Operator Registry**:: A centralized collection of all supported PostScript operators mapped to their implementations.
28
+
29
+ == Class Overview
30
+
31
+ The [`Interpreter`](../../lib/postsvg/interpreter.rb:9) class is defined in [`lib/postsvg/interpreter.rb`](../../lib/postsvg/interpreter.rb:1).
32
+
33
+ **Responsibilities:**
34
+
35
+ * Execute PostScript token streams
36
+ * Manage execution context and state
37
+ * Dispatch operators to command implementations
38
+ * Parse composite objects (procedures, arrays, dictionaries)
39
+ * Generate complete SVG documents
40
+ * Handle errors in strict and lenient modes
41
+
42
+ **Dependencies:**
43
+
44
+ * [`ExecutionContext`](../../lib/postsvg/execution_context.rb:9) - Runtime state management
45
+ * [`Commands::Registry`](../../lib/postsvg/commands/registry.rb:1) - Operator command registry
46
+ * [`Tokenizer`](../../lib/postsvg/tokenizer.rb:8) - Token generation (used by Converter)
47
+
48
+ **Key Design Patterns:**
49
+
50
+ * **Command Pattern** - Operators as executable commands
51
+ * **Strategy Pattern** - Strict vs lenient error handling
52
+ * **State Pattern** - Graphics state management via ExecutionContext
53
+
54
+ == Class Methods
55
+
56
+ === new
57
+
58
+ Create a new Interpreter instance.
59
+
60
+ **Syntax:**
61
+
62
+ [source,ruby]
63
+ ----
64
+ interpreter = Postsvg::Interpreter.new(strict_mode: false) <1>
65
+ ----
66
+ <1> Initialize interpreter with optional strict mode
67
+
68
+ **Where:**
69
+
70
+ `strict_mode`:: (Optional) Boolean enabling strict validation
71
+ * `true` - Fail immediately on unknown operators or execution errors
72
+ * `false` - Continue execution, inserting SVG comments for errors (default)
73
+
74
+ **Returns:**
75
+
76
+ New `Interpreter` instance ready to interpret tokens
77
+
78
+ **Source:**
79
+
80
+ [`lib/postsvg/interpreter.rb:12-18`](../../lib/postsvg/interpreter.rb:12)
81
+
82
+ **Implementation Details:**
83
+
84
+ The constructor initializes:
85
+
86
+ * [`ExecutionContext`](execution-context.adoc) - Runtime state container
87
+ * [`Commands::Registry`](../../lib/postsvg/commands/registry.rb:1) - Default operator registry
88
+ * BoundingBox reference - Initially `nil`, set during interpretation
89
+ * Strict mode flag - Controls error handling behavior
90
+
91
+ .Create interpreter with default settings
92
+ [example]
93
+ ====
94
+ [source,ruby]
95
+ ----
96
+ require 'postsvg'
97
+
98
+ # Create lenient interpreter (default)
99
+ interpreter = Postsvg::Interpreter.new
100
+
101
+ # The interpreter will:
102
+ # - Ignore unknown operators (add comments)
103
+ # - Continue on recoverable errors
104
+ # - Generate SVG even with incomplete PostScript
105
+ ----
106
+ ====
107
+
108
+ .Create strict interpreter
109
+ [example]
110
+ ====
111
+ [source,ruby]
112
+ ----
113
+ require 'postsvg'
114
+
115
+ # Create strict interpreter
116
+ interpreter = Postsvg::Interpreter.new(strict_mode: true)
117
+
118
+ # The interpreter will:
119
+ # - Raise errors on unknown operators
120
+ # - Fail on execution errors
121
+ # - Ensure complete PostScript support
122
+
123
+ begin
124
+ result = interpreter.interpret(tokens, bbox)
125
+ puts "Strict interpretation successful"
126
+ rescue Postsvg::UnsupportedOperatorError => e
127
+ puts "Unsupported: #{e.message}"
128
+ rescue Postsvg::ConversionError => e
129
+ puts "Error: #{e.message}"
130
+ end
131
+ ----
132
+ ====
133
+
134
+ == Instance Methods
135
+
136
+ === interpret
137
+
138
+ Execute a token stream and generate SVG output.
139
+
140
+ **Syntax:**
141
+
142
+ [source,ruby]
143
+ ----
144
+ result = interpreter.interpret(tokens, bounding_box = nil) <1>
145
+ ----
146
+ <1> Process tokens and return result hash
147
+
148
+ **Where:**
149
+
150
+ `tokens`:: Array of token objects from [`Tokenizer`](../../lib/postsvg/tokenizer.rb:8)
151
+ * Each token has `type` and `value` attributes
152
+ * Types: `"number"`, `"string"`, `"operator"`, `"brace"`, `"bracket"`, `"dict"`, etc.
153
+
154
+ `bounding_box`:: (Optional) Hash defining page dimensions
155
+ * `:llx` - Lower-left X coordinate
156
+ * `:lly` - Lower-left Y coordinate
157
+ * `:urx` - Upper-right X coordinate
158
+ * `:ury` - Upper-right Y coordinate
159
+ * `:width` - Page width (calculated if not provided)
160
+ * `:height` - Page height (calculated if not provided)
161
+
162
+ **Returns:**
163
+
164
+ Hash containing:
165
+
166
+ * `:svg` - Complete SVG document string
167
+ * `:elements` - Array of SVG path and text elements
168
+ * `:paths` - Array of SVG path elements
169
+ * `:text` - Array of SVG text elements
170
+ * `:defs` - Array of SVG definitions (gradients, patterns, etc.)
171
+
172
+ **Raises:**
173
+
174
+ * [`Postsvg::UnsupportedOperatorError`](../../lib/postsvg/errors.rb:1) - Unknown operator in strict mode
175
+ * [`Postsvg::ConversionError`](../../lib/postsvg/errors.rb:1) - Execution error in strict mode
176
+ * [`Postsvg::Error`](../../lib/postsvg/errors.rb:1) - Other Postsvg-specific errors
177
+
178
+ **Source:**
179
+
180
+ [`lib/postsvg/interpreter.rb:20-73`](../../lib/postsvg/interpreter.rb:20)
181
+
182
+ .Basic interpretation
183
+ [example]
184
+ ====
185
+ [source,ruby]
186
+ ----
187
+ require 'postsvg'
188
+
189
+ # Tokenize PostScript
190
+ ps_content = <<~PS
191
+ %!PS-Adobe-3.0
192
+ %%BoundingBox: 0 0 100 100
193
+ newpath
194
+ 50 50 moveto
195
+ 90 50 lineto
196
+ stroke
197
+ PS
198
+
199
+ tokens = Postsvg::Tokenizer.tokenize(ps_content)
200
+ bbox = { llx: 0, lly: 0, urx: 100, ury: 100, width: 100, height: 100 }
201
+
202
+ # Interpret
203
+ interpreter = Postsvg::Interpreter.new
204
+ result = interpreter.interpret(tokens, bbox)
205
+
206
+ # Access results
207
+ puts result[:svg] # Complete SVG document
208
+ puts result[:paths].length # Number of paths generated
209
+ puts result[:text].length # Number of text elements
210
+ ----
211
+ ====
212
+
213
+ .Interpretation with error handling
214
+ [example]
215
+ ====
216
+ [source,ruby]
217
+ ----
218
+ require 'postsvg'
219
+
220
+ def safe_interpret(tokens, bbox, strict: false)
221
+ interpreter = Postsvg::Interpreter.new(strict_mode: strict)
222
+ result = interpreter.interpret(tokens, bbox)
223
+
224
+ {
225
+ success: true,
226
+ svg: result[:svg],
227
+ elements: result[:elements].length,
228
+ paths: result[:paths].length,
229
+ text: result[:text].length
230
+ }
231
+ rescue Postsvg::UnsupportedOperatorError => e
232
+ {
233
+ success: false,
234
+ error: "Unsupported operator: #{e.message}",
235
+ type: :unsupported_operator
236
+ }
237
+ rescue Postsvg::ConversionError => e
238
+ {
239
+ success: false,
240
+ error: "Conversion error: #{e.message}",
241
+ type: :conversion_error
242
+ }
243
+ end
244
+
245
+ # Try strict interpretation first
246
+ result = safe_interpret(tokens, bbox, strict: true)
247
+
248
+ if result[:success]
249
+ puts "✓ Strict interpretation: #{result[:elements]} elements"
250
+ else
251
+ puts "✗ Strict failed: #{result[:error]}"
252
+
253
+ # Fall back to lenient mode
254
+ result = safe_interpret(tokens, bbox, strict: false)
255
+ puts "→ Lenient mode: #{result[:success] ? 'Success' : 'Failed'}"
256
+ end
257
+ ----
258
+ ====
259
+
260
+ **Interpretation Process:**
261
+
262
+ The `interpret` method processes tokens in sequence:
263
+
264
+ 1. **Number Tokens** - Pushed to operand stack (preserving int/float distinction)
265
+ 2. **String Tokens** - Pushed to operand stack as string values
266
+ 3. **Brace Tokens** `{...}` - Parsed as procedures and pushed as procedure objects
267
+ 4. **Bracket Tokens** `[...]` - Parsed as arrays and pushed to stack
268
+ 5. **Dict Tokens** `<<...>>` - Parsed as dictionaries and pushed to stack
269
+ 6. **Operator Tokens** - Dispatched to command implementations or dictionary lookups
270
+
271
+ .Token processing example
272
+ [example]
273
+ ====
274
+ [source,ruby]
275
+ ----
276
+ # PostScript: 10 20 add
277
+ # Token stream: [number(10), number(20), operator(add)]
278
+
279
+ # Execution steps:
280
+ # 1. Push 10 onto stack → Stack: [10]
281
+ # 2. Push 20 onto stack → Stack: [10, 20]
282
+ # 3. Execute 'add' operator → Stack: [30]
283
+ # - Pops 20 and 10
284
+ # - Adds them: 10 + 20 = 30
285
+ # - Pushes result: 30
286
+ ----
287
+ ====
288
+
289
+ == Attributes
290
+
291
+ === strict_mode (read/write)
292
+
293
+ Get or set the strict mode flag.
294
+
295
+ **Syntax:**
296
+
297
+ [source,ruby]
298
+ ----
299
+ is_strict = interpreter.strict_mode <1>
300
+ interpreter.strict_mode = true <2>
301
+ ----
302
+ <1> Get current strict mode setting
303
+ <2> Enable strict mode after initialization
304
+
305
+ **Returns:**
306
+
307
+ Boolean (`true` if strict mode enabled, `false` otherwise)
308
+
309
+ **Source:**
310
+
311
+ [`lib/postsvg/interpreter.rb:10`](../../lib/postsvg/interpreter.rb:10)
312
+
313
+ .Toggle strict mode
314
+ [example]
315
+ ====
316
+ [source,ruby]
317
+ ----
318
+ interpreter = Postsvg::Interpreter.new
319
+
320
+ # Check initial setting
321
+ puts "Strict: #{interpreter.strict_mode}" # false
322
+
323
+ # Try lenient interpretation
324
+ begin
325
+ result = interpreter.interpret(tokens, bbox)
326
+ puts "Lenient success: #{result[:paths].length} paths"
327
+ rescue => e
328
+ puts "Unexpected error: #{e.message}"
329
+ end
330
+
331
+ # Enable strict mode for validation
332
+ interpreter.strict_mode = true
333
+
334
+ begin
335
+ result = interpreter.interpret(tokens, bbox)
336
+ puts "Strict success: all operators supported"
337
+ rescue Postsvg::UnsupportedOperatorError => e
338
+ puts "Missing support: #{e.message}"
339
+ end
340
+ ----
341
+ ====
342
+
343
+ == Token Processing
344
+
345
+ === Number Tokens
346
+
347
+ Numbers are pushed to the stack preserving integer vs. float distinction.
348
+
349
+ **Integer Detection:**
350
+
351
+ [source,ruby]
352
+ ----
353
+ # Integers: no decimal point or exponent
354
+ 10 # → 10 (Integer)
355
+ -5 # → -5 (Integer)
356
+ 0 # → 0 (Integer)
357
+
358
+ # Floats: has decimal point or exponent
359
+ 10.5 # → 10.5 (Float)
360
+ 3.0 # → 3.0 (Float)
361
+ 1e3 # → 1000.0 (Float)
362
+ ----
363
+
364
+ .Number processing
365
+ [example]
366
+ ====
367
+ [source,ruby]
368
+ ----
369
+ # PostScript uses both integers and floats
370
+ # Interpreter preserves the distinction
371
+
372
+ # Integer coordinates (common for simple shapes)
373
+ # PostScript: 100 200 moveto
374
+ # Result: Precise pixel-perfect positioning
375
+
376
+ # Float coordinates (common for complex paths)
377
+ # PostScript: 100.5 200.75 moveto
378
+ # Result: Sub-pixel positioning for smooth curves
379
+ ----
380
+ ====
381
+
382
+ === Procedure Tokens
383
+
384
+ Procedures (code blocks) are parsed and stored as procedure objects.
385
+
386
+ **Syntax:**
387
+
388
+ [source]
389
+ ----
390
+ { procedure_body } <1>
391
+ ----
392
+ <1> Brace-delimited code block
393
+
394
+ **Internal Representation:**
395
+
396
+ [source,ruby]
397
+ ----
398
+ {
399
+ type: "procedure",
400
+ body: [token1, token2, ...] # Array of tokens in procedure
401
+ }
402
+ ----
403
+
404
+ .Procedure parsing
405
+ [example]
406
+ ====
407
+ [source,ruby]
408
+ ----
409
+ # PostScript with procedure
410
+ ps_code = <<~PS
411
+ /myproc {
412
+ 100 100 moveto
413
+ 200 100 lineto
414
+ stroke
415
+ } def
416
+
417
+ myproc # Execute the procedure
418
+ PS
419
+
420
+ # The interpreter:
421
+ # 1. Parses { ... } into procedure object
422
+ # 2. Stores in dictionary as /myproc
423
+ # 3. When 'myproc' is encountered, executes the tokens
424
+ ----
425
+ ====
426
+
427
+ === Array Tokens
428
+
429
+ Arrays are parsed and pushed as Ruby arrays.
430
+
431
+ **Syntax:**
432
+
433
+ [source]
434
+ ----
435
+ [ element1 element2 ... ] <1>
436
+ ----
437
+ <1> Bracket-delimited array
438
+
439
+ .Array processing
440
+ [example]
441
+ ====
442
+ [source,ruby]
443
+ ----
444
+ # PostScript arrays
445
+ # [ 1 2 3 ] → [1, 2, 3]
446
+ # [ /name1 /name2 ] → ["name1", "name2"]
447
+ # [ 1.5 2.5 3.5 ] → [1.5, 2.5, 3.5]
448
+
449
+ # Common use: transformation matrices
450
+ # [1 0 0 1 10 20] concat # Translation matrix
451
+ ----
452
+ ====
453
+
454
+ === Dictionary Tokens
455
+
456
+ Dictionaries are parsed as Ruby hashes.
457
+
458
+ **Syntax:**
459
+
460
+ [source]
461
+ ----
462
+ << /key1 value1 /key2 value2 >> <1>
463
+ ----
464
+ <1> Double-angle-bracket delimited dictionary
465
+
466
+ .Dictionary processing
467
+ [example]
468
+ ====
469
+ [source,ruby]
470
+ ----
471
+ # PostScript dictionary
472
+ ps_dict = "<< /Type /Pattern /Width 100 /Height 200 >>"
473
+
474
+ # Parsed as Ruby hash:
475
+ # {
476
+ # "Type" => "Pattern",
477
+ # "Width" => 100,
478
+ # "Height" => 200
479
+ # }
480
+ ----
481
+ ====
482
+
483
+ == Operator Execution
484
+
485
+ === Operator Resolution
486
+
487
+ The interpreter resolves operators in this order:
488
+
489
+ 1. **Dictionary Lookup** - Check if operator is defined in dictionary stack
490
+ 2. **Registry Lookup** - Check built-in command registry
491
+ 3. **Error Handling** - Strict mode raises error, lenient mode adds comment
492
+
493
+ .Operator resolution
494
+ [example]
495
+ ====
496
+ [source,ruby]
497
+ ----
498
+ # PostScript with custom operator
499
+ ps_code = <<~PS
500
+ /myop { 2 mul } def # Define custom operator
501
+ 10 myop # Execute: 10 * 2 = 20
502
+ PS
503
+
504
+ # Resolution process:
505
+ # 1. 'myop' not in built-in registry
506
+ # 2. Found in dictionary as procedure
507
+ # 3. Procedure tokens are executed
508
+ # 4. Result: 20 on stack
509
+ ----
510
+ ====
511
+
512
+ === Built-in Operators
513
+
514
+ The interpreter supports extensive PostScript operator sets:
515
+
516
+ **Graphics Operators:**
517
+ * Path construction: `moveto`, `lineto`, `curveto`, `closepath`
518
+ * Painting: `stroke`, `fill`, `clip`
519
+ * Graphics state: `gsave`, `grestore`, `setlinewidth`
520
+
521
+ **Stack Operators:**
522
+ * Manipulation: `pop`, `dup`, `exch`, `roll`, `copy`
523
+ * Inspection: `count`, `index`, `mark`
524
+
525
+ **Arithmetic Operators:**
526
+ * Basic: `add`, `sub`, `mul`, `div`, `mod`
527
+ * Advanced: `sin`, `cos`, `atan`, `sqrt`, `exp`, `ln`
528
+
529
+ **Control Operators:**
530
+ * Flow: `if`, `ifelse`, `for`, `repeat`, `loop`
531
+ * Execution: `exec`, `bind`, `load`
532
+
533
+ For complete list, see [`Commands::Registry`](../../lib/postsvg/commands/registry.rb:1).
534
+
535
+ == Error Handling Modes
536
+
537
+ === Strict Mode
538
+
539
+ Fails immediately on any unknown operator or execution error.
540
+
541
+ **Behavior:**
542
+
543
+ [source,ruby]
544
+ ----
545
+ # Raises Postsvg::UnsupportedOperatorError
546
+ # or Postsvg::ConversionError
547
+ ----
548
+
549
+ **Use Cases:**
550
+
551
+ * Development and testing
552
+ * Operator support validation
553
+ * Quality assurance
554
+ * Debugging
555
+
556
+ .Strict mode error handling
557
+ [example]
558
+ ====
559
+ [source,ruby]
560
+ ----
561
+ interpreter = Postsvg::Interpreter.new(strict_mode: true)
562
+
563
+ begin
564
+ result = interpreter.interpret(tokens, bbox)
565
+ # All operators supported - safe to use
566
+ rescue Postsvg::UnsupportedOperatorError => e
567
+ puts "Unsupported operator: #{e.message}"
568
+ # Example: "Unknown PostScript operator: shfill"
569
+ rescue Postsvg::ConversionError => e
570
+ puts "Execution error: #{e.message}"
571
+ # Example: "Error executing operator 'moveto': insufficient operands"
572
+ end
573
+ ----
574
+ ====
575
+
576
+ === Lenient Mode
577
+
578
+ Continues execution, inserting SVG comments for errors.
579
+
580
+ **Behavior:**
581
+
582
+ [source,ruby]
583
+ ----
584
+ # Unknown operator → <!-- Unhandled operator: name -->
585
+ # Execution error → <!-- Error executing op: message -->
586
+ ----
587
+
588
+ **Use Cases:**
589
+
590
+ * Production conversion of imperfect PostScript
591
+ * Best-effort SVG generation
592
+ * Handling legacy files
593
+ * Preview generation
594
+
595
+ .Lenient mode behavior
596
+ [example]
597
+ ====
598
+ [source,ruby]
599
+ ----
600
+ interpreter = Postsvg::Interpreter.new(strict_mode: false)
601
+
602
+ result = interpreter.interpret(tokens, bbox)
603
+
604
+ # Result SVG may contain comments like:
605
+ # <!-- Unhandled operator: customop -->
606
+ # <!-- Error executing moveto: stack underflow -->
607
+
608
+ # But conversion completes and returns valid SVG
609
+ puts "Generated SVG with #{result[:paths].length} paths"
610
+
611
+ # Inspect for warnings
612
+ warnings = result[:svg].scan(/<!-- .* -->/)
613
+ puts "Warnings: #{warnings.length}"
614
+ ----
615
+ ====
616
+
617
+ == SVG Generation
618
+
619
+ === Document Structure
620
+
621
+ The interpreter generates complete SVG documents:
622
+
623
+ [source,xml]
624
+ ----
625
+ <?xml version="1.0" encoding="UTF-8"?>
626
+ <svg xmlns="http://www.w3.org/2000/svg"
627
+ viewBox="llx lly width height"
628
+ width="width"
629
+ height="height">
630
+ <defs>
631
+ <!-- Patterns, gradients, clip paths -->
632
+ </defs>
633
+ <g transform="translate(0 height) scale(1 -1)">
634
+ <!-- Paths and text elements -->
635
+ </g>
636
+ </svg>
637
+ ----
638
+
639
+ **Coordinate System:**
640
+
641
+ PostScript uses bottom-left origin, SVG uses top-left. The interpreter applies a transform to flip the Y-axis:
642
+
643
+ [source,xml]
644
+ ----
645
+ <g transform="translate(0 height) scale(1 -1)">
646
+ ----
647
+
648
+ .SVG generation
649
+ [example]
650
+ ====
651
+ [source,ruby]
652
+ ----
653
+ result = interpreter.interpret(tokens, bbox)
654
+
655
+ # Complete SVG document
656
+ svg_doc = result[:svg]
657
+
658
+ # Individual components
659
+ defs = result[:defs] # Patterns, gradients
660
+ paths = result[:paths] # Path elements
661
+ text = result[:text] # Text elements
662
+ elements = result[:elements] # All elements (paths + text)
663
+
664
+ puts "Generated:"
665
+ puts " Definitions: #{defs.length}"
666
+ puts " Paths: #{paths.length}"
667
+ puts " Text: #{text.length}"
668
+ ----
669
+ ====
670
+
671
+ === BoundingBox Handling
672
+
673
+ If no bounding box is provided, defaults are used:
674
+
675
+ [source,ruby]
676
+ ----
677
+ {
678
+ llx: 0,
679
+ lly: 0,
680
+ urx: 612,
681
+ ury: 792,
682
+ width: 612,
683
+ height: 792
684
+ }
685
+ ----
686
+
687
+ This corresponds to a standard US Letter page (8.5" × 11" at 72 DPI).
688
+
689
+ ## Thread Safety
690
+
691
+ The `Interpreter` class is **not thread-safe**. Each thread should have its own instance.
692
+
693
+ .Correct multi-threaded usage
694
+ [example]
695
+ ====
696
+ [source,ruby]
697
+ ----
698
+ # Bad: Sharing interpreter
699
+ interpreter = Postsvg::Interpreter.new
700
+ threads = 5.times.map do
701
+ Thread.new { interpreter.interpret(tokens, bbox) } # NOT SAFE
702
+ end
703
+
704
+ # Good: Each thread creates own interpreter
705
+ token_sets = load_token_sets()
706
+ threads = token_sets.map do |tokens|
707
+ Thread.new do
708
+ interpreter = Postsvg::Interpreter.new
709
+ interpreter.interpret(tokens, bbox)
710
+ end
711
+ end
712
+
713
+ results = threads.map(&:value)
714
+ ----
715
+ ====
716
+
717
+ == Performance Characteristics
718
+
719
+ **Time Complexity:**
720
+
721
+ * Token iteration: O(n) where n = token count
722
+ * Operator dispatch: O(1) via hash lookup
723
+ * Procedure execution: O(m) where m = procedure token count
724
+ * Overall: O(n + m) linear in total tokens
725
+
726
+ **Space Complexity:**
727
+
728
+ * Stack depth: O(s) where s = max stack depth
729
+ * Dictionary stack: O(d) where d = dictionary entries
730
+ * Graphics state stack: O(g) where g = gsave/grestore nesting
731
+ * Path data: O(p) where p = path complexity
732
+
733
+ .Performance monitoring
734
+ [example]
735
+ ====
736
+ [source,ruby]
737
+ ----
738
+ require 'postsvg'
739
+ require 'benchmark'
740
+
741
+ tokens = Postsvg::Tokenizer.tokenize(ps_content)
742
+ bbox = extract_bbox(ps_content)
743
+
744
+ puts "Tokens: #{tokens.length}"
745
+
746
+ time = Benchmark.measure do
747
+ interpreter = Postsvg::Interpreter.new
748
+ @result = interpreter.interpret(tokens, bbox)
749
+ end
750
+
751
+ puts "Interpretation time: #{'%.3f' % time.real}s"
752
+ puts "Paths generated: #{@result[:paths].length}"
753
+ puts "Throughput: #{(tokens.length / time.real).to_i} tokens/sec"
754
+ ----
755
+ ====
756
+
757
+ == Advanced Usage
758
+
759
+ === Custom Operator Registry
760
+
761
+ You can provide a custom registry for extending operator support:
762
+
763
+ [source,ruby]
764
+ ----
765
+ # Create custom registry
766
+ registry = Postsvg::Commands::Registry.new
767
+
768
+ # Add custom operators
769
+ registry.register('myop', MyCustomCommand.new)
770
+
771
+ # Create interpreter with custom registry
772
+ interpreter = Postsvg::Interpreter.new
773
+ interpreter.instance_variable_set(:@registry, registry)
774
+ ----
775
+
776
+ === Accessing Execution Context
777
+
778
+ The execution context is accessible for advanced use cases:
779
+
780
+ [source,ruby]
781
+ ----
782
+ interpreter = Postsvg::Interpreter.new
783
+ result = interpreter.interpret(tokens, bbox)
784
+
785
+ # Access internal context (private, use with caution)
786
+ context = interpreter.instance_variable_get(:@context)
787
+
788
+ # Inspect final stack
789
+ stack = context.stack
790
+ puts "Final stack: #{stack.inspect}"
791
+
792
+ # Inspect dictionaries
793
+ dicts = context.dict_stack
794
+ puts "Dictionary entries: #{dicts.last.keys.join(', ')}"
795
+ ----
796
+
797
+ == Next Steps
798
+
799
+ * Learn about link:execution-context.adoc[ExecutionContext] for state management
800
+ * Review link:graphics-state.adoc[GraphicsState] for graphics state details
801
+ * See link:converter.adoc[Converter Class] for high-level conversion API
802
+ * Check link:../architecture.adoc[Architecture] for system design
803
+
804
+ == Bibliography
805
+
806
+ * link:converter.adoc[Converter Class Documentation]
807
+ * link:execution-context.adoc[ExecutionContext Documentation]
808
+ * link:graphics-state.adoc[GraphicsState Documentation]
809
+ * link:../architecture.adoc[Architecture Overview]
810
+ * link:https://www.adobe.com/jp/print/postscript/pdfs/PLRM.pdf[PostScript Language Reference Manual]