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,1125 @@
1
+ = Interpreter Stage
2
+ :page-nav_order: 3
3
+
4
+ == Purpose
5
+
6
+ This document describes the Interpreter Stage of Postsvg's conversion pipeline, which executes PostScript commands and builds the SVG representation. Understanding the interpreter helps developers comprehend command execution, stack management, dictionary operations, and how PostScript semantics are implemented.
7
+
8
+ == References
9
+
10
+ * link:../architecture.adoc[Architecture Overview]
11
+ * link:conversion-pipeline.adoc[Conversion Pipeline]
12
+ * link:parser-stage.adoc[Parser Stage]
13
+ * link:command-registry.adoc[Command Registry]
14
+ * link:graphics-state-model.adoc[Graphics State Model]
15
+ * link:../api-reference/interpreter.adoc[Interpreter API Reference]
16
+ * link:../api-reference/execution-context.adoc[ExecutionContext API Reference]
17
+
18
+ == Concepts
19
+
20
+ **Stack-Based Execution**:: PostScript uses an operand stack for passing parameters to operators.
21
+
22
+ **Command Pattern**:: Each PostScript operator is implemented as a command object.
23
+
24
+ **Execution Context**:: Encapsulates all mutable state during execution (stack, dictionaries, graphics state).
25
+
26
+ **Dictionary Stack**:: Maintains name-to-value bindings with nested scoping.
27
+
28
+ **Procedure Execution**:: Executable arrays (procedures) are expanded inline for execution.
29
+
30
+ == Interpreter Architecture
31
+
32
+ === Class Structure
33
+
34
+ **Location:** [`lib/postsvg/interpreter.rb`](../../lib/postsvg/interpreter.rb:9)
35
+
36
+ **Responsibilities:**
37
+
38
+ * Process tokens sequentially
39
+ * Execute PostScript operators
40
+ * Manage execution context
41
+ * Parse composite data structures
42
+ * Generate final SVG document
43
+
44
+ === Key Components
45
+
46
+ **Interpreter**:: Main execution loop and token dispatcher.
47
+
48
+ **ExecutionContext**:: Manages operand stack, dictionary stack, graphics state.
49
+
50
+ **Command Registry**:: Maps operator names to command implementations.
51
+
52
+ **Graphics State**:: Tracks path, colors, transformations, and rendering parameters.
53
+
54
+ === Initialization
55
+
56
+ [source,ruby]
57
+ ----
58
+ # lib/postsvg/interpreter.rb:12
59
+ def initialize(strict_mode: false)
60
+ @context = ExecutionContext.new
61
+ @registry = Commands::Registry.default
62
+ @context.registry = @registry
63
+ @bbox = nil
64
+ @strict_mode = strict_mode
65
+ end
66
+ ----
67
+
68
+ **Initialization Steps:**
69
+
70
+ 1. Create fresh [`ExecutionContext`](../../lib/postsvg/execution_context.rb:9)
71
+ 2. Load default command registry
72
+ 3. Configure strict/lenient error handling
73
+ 4. Initialize state tracking variables
74
+
75
+ == Main Execution Loop
76
+
77
+ === Interpret Method
78
+
79
+ [source,ruby]
80
+ ----
81
+ # lib/postsvg/interpreter.rb:20
82
+ def interpret(tokens, bounding_box = nil)
83
+ @bbox = bounding_box
84
+ @context.instance_variable_set(:@bbox, bounding_box)
85
+ i = 0
86
+
87
+ while i < tokens.length
88
+ token = tokens[i]
89
+
90
+ case token.type
91
+ when "number"
92
+ # Push number onto operand stack
93
+ num = parse_number(token.value)
94
+ @context.push(num)
95
+
96
+ when "string", "hexstring", "name"
97
+ # Push literal onto stack
98
+ @context.push(token.value)
99
+
100
+ when "brace"
101
+ # Parse procedure: { ... }
102
+ if token.value == "{"
103
+ proc_result = parse_procedure(tokens, i + 1)
104
+ @context.push({ type: "procedure", body: proc_result[:procedure] })
105
+ i = proc_result[:next_index] - 1
106
+ end
107
+
108
+ when "bracket"
109
+ # Parse array: [ ... ]
110
+ if token.value == "["
111
+ array_result = parse_array(tokens, i + 1)
112
+ @context.push(array_result[:array])
113
+ i = array_result[:next_index] - 1
114
+ end
115
+
116
+ when "dict"
117
+ # Parse dictionary: << ... >>
118
+ if token.value == "<<"
119
+ dict_result = parse_dict(tokens, i + 1)
120
+ @context.push(dict_result[:dict])
121
+ i = dict_result[:next_index] - 1
122
+ end
123
+
124
+ when "operator"
125
+ # Execute PostScript operator
126
+ execute_operator(token.value, tokens, i)
127
+ end
128
+
129
+ i += 1
130
+ end
131
+
132
+ # Generate final SVG document
133
+ svg_doc = generate_svg_document(@context.svg_output, @bbox)
134
+ { svg: svg_doc, elements: @context.svg_output[:paths] + @context.svg_output[:text] }
135
+ end
136
+ ----
137
+
138
+ === Token Dispatching
139
+
140
+ **Token Type → Action Mapping:**
141
+
142
+ [cols="1,2,2"]
143
+ |===
144
+ |Token Type |Action |Example
145
+
146
+ |`number`
147
+ |Parse and push onto stack
148
+ |`42` → Push 42
149
+
150
+ |`string`/`name`
151
+ |Push literal onto stack
152
+ |`/Helvetica` → Push "Helvetica"
153
+
154
+ |`brace`
155
+ |Parse procedure, push executable array
156
+ |`{ 1 add }` → Push procedure object
157
+
158
+ |`bracket`
159
+ |Parse array, push array object
160
+ |`[1 2 3]` → Push [1, 2, 3]
161
+
162
+ |`dict`
163
+ |Parse dictionary, push dict object
164
+ |`<< /Type /Pattern >>` → Push hash
165
+
166
+ |`operator`
167
+ |Look up and execute command
168
+ |`moveto` → Execute MoveTo command
169
+ |===
170
+
171
+ == Number Parsing
172
+
173
+ === Integer vs Float Detection
174
+
175
+ [source,ruby]
176
+ ----
177
+ # lib/postsvg/interpreter.rb:29
178
+ num_str = token.value
179
+ num = if num_str.include?(".") || num_str.match?(/[eE]/)
180
+ num_str.to_f # Float
181
+ else
182
+ num_str.to_i # Integer
183
+ end
184
+ @context.push(num)
185
+ ----
186
+
187
+ **Decision Rules:**
188
+
189
+ * Contains `.` → Convert to Float
190
+ * Contains `e` or `E` → Convert to Float (scientific notation)
191
+ * Otherwise → Convert to Integer
192
+
193
+ **Examples:**
194
+
195
+ [source]
196
+ ----
197
+ Token("42") → 42 (Integer)
198
+ Token("3.14") → 3.14 (Float)
199
+ Token("1.5e-3") → 0.0015 (Float)
200
+ Token("100.") → 100.0 (Float)
201
+ ----
202
+
203
+ **Rationale:** Preserving integer vs float distinction ensures PostScript semantics are maintained (e.g., integer division vs float division).
204
+
205
+ == Composite Data Structure Parsing
206
+
207
+ === Procedure Parsing
208
+
209
+ **Syntax:** `{ tokens... }`
210
+
211
+ **Purpose:** Create executable array (deferred execution).
212
+
213
+ [source,ruby]
214
+ ----
215
+ # lib/postsvg/interpreter.rb:77
216
+ def parse_procedure(tokens, start_index)
217
+ procedure = []
218
+ depth = 1
219
+ index = start_index
220
+
221
+ while index < tokens.length && depth > 0
222
+ token = tokens[index]
223
+
224
+ # Track nesting depth
225
+ if token.type == "brace" && token.value == "{"
226
+ depth += 1
227
+ elsif token.type == "brace" && token.value == "}"
228
+ depth -= 1
229
+ return { procedure: procedure, next_index: index + 1 } if depth == 0
230
+ end
231
+
232
+ # Add token to procedure body if inside braces
233
+ procedure << token if depth > 0
234
+ index += 1
235
+ end
236
+
237
+ { procedure: procedure, next_index: index }
238
+ end
239
+ ----
240
+
241
+ **Features:**
242
+
243
+ * **Nested Procedures:** Tracks depth for `{ { nested } }`
244
+ * **Token Preservation:** Stores raw tokens for later execution
245
+ * **Boundary Detection:** Returns position after closing `}`
246
+
247
+ **Example:**
248
+
249
+ [source,postscript]
250
+ ----
251
+ { 10 20 moveto 30 40 lineto stroke }
252
+ ----
253
+
254
+ **Result:**
255
+
256
+ [source,ruby]
257
+ ----
258
+ {
259
+ type: "procedure",
260
+ body: [
261
+ Token(:number, "10"),
262
+ Token(:number, "20"),
263
+ Token(:operator, "moveto"),
264
+ Token(:number, "30"),
265
+ Token(:number, "40"),
266
+ Token(:operator, "lineto"),
267
+ Token(:operator, "stroke")
268
+ ]
269
+ }
270
+ ----
271
+
272
+ === Array Parsing
273
+
274
+ **Syntax:** `[ elements... ]`
275
+
276
+ **Purpose:** Create array of values (immediately evaluated).
277
+
278
+ [source,ruby]
279
+ ----
280
+ # lib/postsvg/interpreter.rb:97
281
+ def parse_array(tokens, start_index)
282
+ array = []
283
+ index = start_index
284
+
285
+ while index < tokens.length
286
+ token = tokens[index]
287
+
288
+ # Check for array end
289
+ return { array: array, next_index: index + 1 } if token.value == "]"
290
+
291
+ # Parse and add element
292
+ if token.type == "number"
293
+ array << parse_number(token.value)
294
+ elsif ["string", "name"].include?(token.type)
295
+ array << token.value
296
+ end
297
+
298
+ index += 1
299
+ end
300
+
301
+ { array: array, next_index: index }
302
+ end
303
+ ----
304
+
305
+ **Features:**
306
+
307
+ * **Immediate Evaluation:** Elements are parsed during array construction
308
+ * **Type Preservation:** Numbers, strings, names are converted to Ruby types
309
+ * **Flat Structure:** Arrays don't contain nested tokens (unlike procedures)
310
+
311
+ **Example:**
312
+
313
+ [source,postscript]
314
+ ----
315
+ [1 2.5 /FontName (Hello)]
316
+ ----
317
+
318
+ **Result:**
319
+
320
+ [source,ruby]
321
+ ----
322
+ [1, 2.5, "FontName", "Hello"]
323
+ ----
324
+
325
+ === Dictionary Parsing
326
+
327
+ **Syntax:** `<< /key value... >>`
328
+
329
+ **Purpose:** Create key-value dictionary.
330
+
331
+ [source,ruby]
332
+ ----
333
+ # lib/postsvg/interpreter.rb:125
334
+ def parse_dict(tokens, start_index)
335
+ dict = {}
336
+ index = start_index
337
+ current_key = nil
338
+
339
+ while index < tokens.length
340
+ token = tokens[index]
341
+
342
+ # Check for dictionary end
343
+ return { dict: dict, next_index: index + 1 } if token.value == ">>"
344
+
345
+ # Process key-value pairs
346
+ if token.type == "name"
347
+ current_key = token.value
348
+ elsif current_key
349
+ # Parse value for current key
350
+ dict[current_key] = parse_dict_value(token, tokens, index)
351
+ current_key = nil
352
+ end
353
+
354
+ index += 1
355
+ end
356
+
357
+ { dict: dict, next_index: index }
358
+ end
359
+ ----
360
+
361
+ **Features:**
362
+
363
+ * **Key-Value Pairing:** Alternates between keys (names) and values
364
+ * **Nested Structures:** Supports nested arrays, dicts, procedures
365
+ * **Type Conversion:** Values are parsed based on token type
366
+
367
+ **Example:**
368
+
369
+ [source,postscript]
370
+ ----
371
+ << /Type /Pattern /Matrix [1 0 0 1 0 0] /PaintProc { pop } >>
372
+ ----
373
+
374
+ **Result:**
375
+
376
+ [source,ruby]
377
+ ----
378
+ {
379
+ "Type" => "Pattern",
380
+ "Matrix" => [1, 0, 0, 1, 0, 0],
381
+ "PaintProc" => { type: "procedure", body: [Token(:operator, "pop")] }
382
+ }
383
+ ----
384
+
385
+ == Operator Execution
386
+
387
+ === Execute Operator Method
388
+
389
+ [source,ruby]
390
+ ----
391
+ # lib/postsvg/interpreter.rb:177
392
+ def execute_operator(op, tokens, current_index)
393
+ # Step 1: Check dictionary stack for user-defined operators
394
+ dict_val = @context.lookup(op)
395
+ if dict_val
396
+ if dict_val.is_a?(Hash) && dict_val[:type] == "procedure"
397
+ # Execute user-defined procedure
398
+ execute_procedure(tokens, dict_val[:body], current_index)
399
+ else
400
+ # Push literal value
401
+ @context.push(dict_val)
402
+ end
403
+ return
404
+ end
405
+
406
+ # Step 2: Look up built-in command in registry
407
+ command = @registry.get(op)
408
+ if command
409
+ # Execute command
410
+ begin
411
+ command.call(@context)
412
+ rescue StandardError => e
413
+ handle_execution_error(op, e)
414
+ end
415
+ elsif @strict_mode
416
+ # Unknown operator in strict mode
417
+ raise UnsupportedOperatorError, "Unknown PostScript operator: #{op}"
418
+ else
419
+ # Add comment in lenient mode
420
+ @context.svg_output[:paths] << "<!-- Unhandled operator: #{op} -->"
421
+ end
422
+ end
423
+ ----
424
+
425
+ **Execution Priority:**
426
+
427
+ 1. **User-Defined:** Check dictionary stack first
428
+ 2. **Built-In:** Look up in command registry
429
+ 3. **Error Handling:** Strict mode raises, lenient mode comments
430
+
431
+ === User-Defined Operators
432
+
433
+ **Definition:**
434
+
435
+ [source,postscript]
436
+ ----
437
+ /myop { 2 mul } def
438
+ 5 myop % Executes { 2 mul } with 5 on stack → 10
439
+ ----
440
+
441
+ **Execution:**
442
+
443
+ [source,ruby]
444
+ ----
445
+ # lib/postsvg/interpreter.rb:215
446
+ def execute_procedure(tokens, proc_tokens, current_index)
447
+ # Insert procedure tokens into token stream
448
+ tokens.insert(current_index + 1, *proc_tokens)
449
+ end
450
+ ----
451
+
452
+ **Inline Expansion:** Procedure tokens are inserted directly into the token stream for immediate execution.
453
+
454
+ === Built-In Command Execution
455
+
456
+ **Registry Lookup:**
457
+
458
+ [source,ruby]
459
+ ----
460
+ command = @registry.get("moveto")
461
+ # Returns instance of Commands::Path::MoveTo
462
+ ----
463
+
464
+ **Command Interface:**
465
+
466
+ [source,ruby]
467
+ ----
468
+ class Commands::Path::MoveTo < Commands::Base
469
+ def call(context)
470
+ y = context.pop_number
471
+ x = context.pop_number
472
+ context.path_builder.move_to(x, y)
473
+ context.update_current_point(x, y)
474
+ end
475
+ end
476
+ ----
477
+
478
+ **Execution:** `command.call(@context)` passes execution context to command.
479
+
480
+ === Error Handling
481
+
482
+ **Strict Mode:**
483
+
484
+ [source,ruby]
485
+ ----
486
+ # lib/postsvg/interpreter.rb:196
487
+ if @strict_mode
488
+ raise ConversionError, "Error executing operator '#{op}': #{e.message}"
489
+ end
490
+ ----
491
+
492
+ **Lenient Mode:**
493
+
494
+ [source,ruby]
495
+ ----
496
+ # lib/postsvg/interpreter.rb:201
497
+ @context.svg_output[:paths] << "<!-- Error executing #{op}: #{e.message} -->"
498
+ ----
499
+
500
+ **Trade-off:** Strict mode ensures correctness; lenient mode maximizes compatibility.
501
+
502
+ == Execution Context
503
+
504
+ === Context Architecture
505
+
506
+ **Location:** [`lib/postsvg/execution_context.rb`](../../lib/postsvg/execution_context.rb:9)
507
+
508
+ **Components:**
509
+
510
+ * **Operand Stack:** LIFO stack for values
511
+ * **Dictionary Stack:** Nested name scopes
512
+ * **Graphics State:** Drawing parameters and path
513
+ * **Graphics State Stack:** Saved states for `gsave`/`grestore`
514
+ * **SVG Output Buffer:** Accumulated SVG elements
515
+
516
+ === Operand Stack
517
+
518
+ **Push Operation:**
519
+
520
+ [source,ruby]
521
+ ----
522
+ # lib/postsvg/execution_context.rb:33
523
+ def push(value)
524
+ @stack << value
525
+ end
526
+ ----
527
+
528
+ **Pop Operation:**
529
+
530
+ [source,ruby]
531
+ ----
532
+ # lib/postsvg/execution_context.rb:37
533
+ def pop
534
+ @stack.pop
535
+ end
536
+ ----
537
+
538
+ **Type-Safe Pop:**
539
+
540
+ [source,ruby]
541
+ ----
542
+ # lib/postsvg/execution_context.rb:41
543
+ def pop_number(default = 0)
544
+ v = @stack.pop
545
+ return v if v.is_a?(Numeric)
546
+ return v.to_f if v.is_a?(String) && v.match?(/^-?\d+\.?\d*$/)
547
+ default
548
+ end
549
+ ----
550
+
551
+ **Stack Examples:**
552
+
553
+ [source]
554
+ ----
555
+ Operation: push(10) push(20) pop() push(30)
556
+ Stack State: [10] [10,20] [10] [10,30]
557
+ ----
558
+
559
+ === Dictionary Stack
560
+
561
+ **Structure:**
562
+
563
+ [source,ruby]
564
+ ----
565
+ # lib/postsvg/execution_context.rb:26
566
+ @global_dict = {}
567
+ @dict_stack = [@global_dict]
568
+ ----
569
+
570
+ **Multiple Scopes:**
571
+
572
+ [source]
573
+ ----
574
+ Dictionary Stack:
575
+ [Global Dict, Dict 1, Dict 2, ...]
576
+ ^
577
+ Bottom (always present)
578
+ ----
579
+
580
+ **Define Operation:**
581
+
582
+ [source,ruby]
583
+ ----
584
+ # lib/postsvg/execution_context.rb:93
585
+ def define(key, value)
586
+ @dict_stack.last[key.to_s] = value if key
587
+ end
588
+ ----
589
+
590
+ **Lookup Operation:**
591
+
592
+ [source,ruby]
593
+ ----
594
+ # lib/postsvg/execution_context.rb:96
595
+ def lookup(name)
596
+ @dict_stack.reverse_each do |dict|
597
+ return dict[name] if dict.key?(name)
598
+ end
599
+ nil
600
+ end
601
+ ----
602
+
603
+ **Lookup Priority:** Searches from top to bottom (innermost to outermost scope).
604
+
605
+ **Example:**
606
+
607
+ [source,postscript]
608
+ ----
609
+ /x 10 def % Global scope
610
+ dict begin
611
+ /x 20 def % Local scope
612
+ x % Looks up → 20 (local)
613
+ end
614
+ x % Looks up → 10 (global)
615
+ ----
616
+
617
+ === Graphics State Management
618
+
619
+ **Save State:**
620
+
621
+ [source,ruby]
622
+ ----
623
+ # lib/postsvg/execution_context.rb:54
624
+ def save_graphics_state
625
+ @saved_ctm_at_gsave = clone_matrix(@graphics_state[:ctm])
626
+
627
+ @g_stack << {
628
+ graphics_state: clone_graphics_state(@graphics_state),
629
+ current_x: @current_x,
630
+ current_y: @current_y,
631
+ saved_ctm: @saved_ctm_at_gsave,
632
+ path_builder: @path_builder.dup
633
+ }
634
+ end
635
+ ----
636
+
637
+ **Restore State:**
638
+
639
+ [source,ruby]
640
+ ----
641
+ # lib/postsvg/execution_context.rb:68
642
+ def restore_graphics_state
643
+ return if @g_stack.empty?
644
+
645
+ saved = @g_stack.pop
646
+ @graphics_state = saved[:graphics_state]
647
+ @current_x = saved[:current_x]
648
+ @current_y = saved[:current_y]
649
+ @saved_ctm_at_gsave = saved[:saved_ctm]
650
+ @path_builder = saved[:path_builder]
651
+ end
652
+ ----
653
+
654
+ **State Components:**
655
+
656
+ * Current transformation matrix (CTM)
657
+ * Fill and stroke colors
658
+ * Line width, cap, join, dash pattern
659
+ * Current path
660
+ * Current point (x, y)
661
+ * Clipping path
662
+
663
+ === SVG Output Buffer
664
+
665
+ **Structure:**
666
+
667
+ [source,ruby]
668
+ ----
669
+ # lib/postsvg/execution_context.rb:24
670
+ @svg_output = { defs: [], paths: [], text: [] }
671
+ ----
672
+
673
+ **Buffer Sections:**
674
+
675
+ * `defs` - Definitions (clipPath, patterns, gradients)
676
+ * `paths` - Path elements (`<path>`, `<rect>`, etc.)
677
+ * `text` - Text elements (`<text>`, `<tspan>`)
678
+
679
+ **Adding Elements:**
680
+
681
+ [source,ruby]
682
+ ----
683
+ @context.svg_output[:paths] << "<path d='M 10 20 L 30 40' />"
684
+ @context.svg_output[:defs] << "<clipPath id='clip1'>...</clipPath>"
685
+ @context.svg_output[:text] << "<text x='10' y='20'>Hello</text>"
686
+ ----
687
+
688
+ == Path Building
689
+
690
+ === PathBuilder Architecture
691
+
692
+ **Location:** [`lib/postsvg/path_builder.rb`](../../lib/postsvg/path_builder.rb:7)
693
+
694
+ **Purpose:** Build SVG path data string incrementally.
695
+
696
+ **Interface:**
697
+
698
+ [source,ruby]
699
+ ----
700
+ @path_builder.move_to(x, y) # Add M command
701
+ @path_builder.line_to(x, y) # Add L command
702
+ @path_builder.curve_to(...) # Add C command
703
+ @path_builder.close # Add Z command
704
+ @path_builder.to_path # Generate path string
705
+ ----
706
+
707
+ === Path Commands
708
+
709
+ **MoveTo:**
710
+
711
+ [source,ruby]
712
+ ----
713
+ # lib/postsvg/path_builder.rb:22
714
+ def move_to(x, y)
715
+ @parts << "M #{num_fmt(x)} #{num_fmt(y)}"
716
+ @last_command = :move
717
+ @current_x = x
718
+ @current_y = y
719
+ end
720
+ ----
721
+
722
+ **LineTo:**
723
+
724
+ [source,ruby]
725
+ ----
726
+ # lib/postsvg/path_builder.rb:34
727
+ def line_to(x, y)
728
+ @parts << "L #{num_fmt(x)} #{num_fmt(y)}"
729
+ @last_command = :line
730
+ @current_x = x
731
+ @current_y = y
732
+ end
733
+ ----
734
+
735
+ **CurveTo:**
736
+
737
+ [source,ruby]
738
+ ----
739
+ # lib/postsvg/path_builder.rb:46
740
+ def curve_to(x1, y1, x2, y2, x, y)
741
+ @parts << "C #{num_fmt(x1)} #{num_fmt(y1)} " \
742
+ "#{num_fmt(x2)} #{num_fmt(y2)} " \
743
+ "#{num_fmt(x)} #{num_fmt(y)}"
744
+ @last_command = :curve
745
+ @current_x = x
746
+ @current_y = y
747
+ end
748
+ ----
749
+
750
+ **ClosePath:**
751
+
752
+ [source,ruby]
753
+ ----
754
+ # lib/postsvg/path_builder.rb:70
755
+ def close
756
+ @parts << "Z"
757
+ @last_command = :close
758
+ end
759
+ ----
760
+
761
+ === Path Generation
762
+
763
+ **Example:**
764
+
765
+ [source,postscript]
766
+ ----
767
+ newpath
768
+ 10 20 moveto
769
+ 30 40 lineto
770
+ 50 40 lineto
771
+ closepath
772
+ ----
773
+
774
+ **PathBuilder State:**
775
+
776
+ [source]
777
+ ----
778
+ Step 1: move_to(10, 20) → parts = ["M 10 20"]
779
+ Step 2: line_to(30, 40) → parts = ["M 10 20", "L 30 40"]
780
+ Step 3: line_to(50, 40) → parts = ["M 10 20", "L 30 40", "L 50 40"]
781
+ Step 4: close → parts = ["M 10 20", "L 30 40", "L 50 40", "Z"]
782
+ ----
783
+
784
+ **Output:**
785
+
786
+ [source]
787
+ ----
788
+ @path_builder.to_path → "M 10 20 L 30 40 L 50 40 Z"
789
+ ----
790
+
791
+ == Coordinate Transformation
792
+
793
+ === Matrix Operations
794
+
795
+ **Location:** [`lib/postsvg/matrix.rb`](../../lib/postsvg/matrix.rb:6)
796
+
797
+ **Representation:** Affine transformation matrix `[a b c d e f]`
798
+
799
+ **Matrix Multiplication:**
800
+
801
+ [source,ruby]
802
+ ----
803
+ # lib/postsvg/matrix.rb:18
804
+ def multiply(matrix)
805
+ result = Matrix.new
806
+ result.a = (@a * matrix.a) + (@c * matrix.b)
807
+ result.b = (@b * matrix.a) + (@d * matrix.b)
808
+ result.c = (@a * matrix.c) + (@c * matrix.d)
809
+ result.d = (@b * matrix.c) + (@d * matrix.d)
810
+ result.e = (@a * matrix.e) + (@c * matrix.f) + @e
811
+ result.f = (@b * matrix.e) + (@d * matrix.f) + @f
812
+ result
813
+ end
814
+ ----
815
+
816
+ === Transformation Commands
817
+
818
+ **Translate:**
819
+
820
+ [source,ruby]
821
+ ----
822
+ # lib/postsvg/matrix.rb:29
823
+ def translate(tx, ty)
824
+ multiply(Matrix.new(e: tx, f: ty))
825
+ end
826
+ ----
827
+
828
+ **Scale:**
829
+
830
+ [source,ruby]
831
+ ----
832
+ # lib/postsvg/matrix.rb:33
833
+ def scale(sx, sy)
834
+ multiply(Matrix.new(a: sx, d: sy))
835
+ end
836
+ ----
837
+
838
+ **Rotate:**
839
+
840
+ [source,ruby]
841
+ ----
842
+ # lib/postsvg/matrix.rb:37
843
+ def rotate(degrees)
844
+ radians = degrees * Math::PI / 180.0
845
+ m = Matrix.new
846
+ m.a = Math.cos(radians)
847
+ m.b = Math.sin(radians)
848
+ m.c = -Math.sin(radians)
849
+ m.d = Math.cos(radians)
850
+ multiply(m)
851
+ end
852
+ ----
853
+
854
+ === Point Transformation
855
+
856
+ **Apply Matrix to Point:**
857
+
858
+ [source,ruby]
859
+ ----
860
+ # lib/postsvg/matrix.rb:61
861
+ def apply_point(x, y)
862
+ {
863
+ x: (x * @a) + (y * @c) + @e,
864
+ y: (x * @b) + (y * @d) + @f
865
+ }
866
+ end
867
+ ----
868
+
869
+ **Example:**
870
+
871
+ [source]
872
+ ----
873
+ Matrix: [2, 0, 0, 2, 10, 20] # scale(2, 2) + translate(10, 20)
874
+ Point: (5, 5)
875
+
876
+ Transformed: x' = (5 * 2) + (5 * 0) + 10 = 20
877
+ y' = (5 * 0) + (5 * 2) + 20 = 30
878
+ Result: (20, 30)
879
+ ----
880
+
881
+ == SVG Generation
882
+
883
+ === Document Assembly
884
+
885
+ [source,ruby]
886
+ ----
887
+ # lib/postsvg/interpreter.rb:219
888
+ def generate_svg_document(svg_out, bbox)
889
+ width = num_fmt(bbox[:width])
890
+ height = num_fmt(bbox[:height])
891
+ llx = num_fmt(bbox[:llx])
892
+ lly = num_fmt(bbox[:lly])
893
+
894
+ view_box = "#{llx} #{lly} #{width} #{height}"
895
+
896
+ # Build <defs> section
897
+ defs = svg_out[:defs].empty? ? "" :
898
+ "\n<defs>\n#{svg_out[:defs].join("\n")}\n</defs>\n"
899
+
900
+ # Combine paths and text
901
+ elements = (svg_out[:paths] + svg_out[:text]).join("\n")
902
+
903
+ # Apply global Y-axis flip
904
+ transform = "translate(0 #{height}) scale(1 -1)"
905
+ body = "\n<g transform=\"#{transform}\">\n#{elements}\n</g>"
906
+
907
+ # Assemble document
908
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
909
+ "<svg xmlns=\"http://www.w3.org/2000/svg\" " \
910
+ "viewBox=\"#{view_box}\" width=\"#{width}\" height=\"#{height}\">" \
911
+ "#{defs}#{body}\n</svg>"
912
+ end
913
+ ----
914
+
915
+ === Number Formatting
916
+
917
+ **Purpose:** Format numbers for SVG output (remove trailing zeros, handle edge cases).
918
+
919
+ [source,ruby]
920
+ ----
921
+ # lib/postsvg/interpreter.rb:246
922
+ def num_fmt(n)
923
+ return "0" if n.nil?
924
+ return "0" if n.respond_to?(:nan?) && n.nan?
925
+ return "0" if n.respond_to?(:infinite?) && n.infinite?
926
+
927
+ n = n.to_f
928
+
929
+ # Check if effectively an integer
930
+ if (n - n.round).abs < 1e-10
931
+ n.round.to_i.to_s
932
+ else
933
+ # Format with up to 3 decimals, strip trailing zeros
934
+ format("%.3f", n).sub(/\.?0+$/, "")
935
+ end
936
+ end
937
+ ----
938
+
939
+ **Examples:**
940
+
941
+ [source]
942
+ ----
943
+ num_fmt(10.0) → "10"
944
+ num_fmt(10.5) → "10.5"
945
+ num_fmt(10.500) → "10.5"
946
+ num_fmt(10.123456) → "10.123"
947
+ num_fmt(0.0001) → "0"
948
+ ----
949
+
950
+ == Execution Example
951
+
952
+ === Input PostScript
953
+
954
+ [source,postscript]
955
+ ----
956
+ newpath
957
+ 50 50 moveto
958
+ 100 50 lineto
959
+ 100 100 lineto
960
+ 50 100 lineto
961
+ closepath
962
+ 0.5 setgray
963
+ fill
964
+ ----
965
+
966
+ === Execution Trace
967
+
968
+ [source]
969
+ ----
970
+ Token Action Stack State Path State
971
+ ================================================================================
972
+ newpath Clear path [] []
973
+ 50 Push [50] []
974
+ 50 Push [50, 50] []
975
+ moveto Pop 50, 50; M command [] ["M 50 50"]
976
+ 100 Push [100] ["M 50 50"]
977
+ 50 Push [100, 50] ["M 50 50"]
978
+ lineto Pop 100, 50; L command [] ["M 50 50", "L 100 50"]
979
+ 100 Push [100] [...]
980
+ 100 Push [100, 100] [...]
981
+ lineto Pop 100, 100; L command [] [..., "L 100 100"]
982
+ 50 Push [50] [...]
983
+ 100 Push [50, 100] [...]
984
+ lineto Pop 50, 100; L command [] [..., "L 50 100"]
985
+ closepath Z command [] [..., "Z"]
986
+ 0.5 Push [0.5] [...]
987
+ setgray Pop 0.5; Set color [] [...]
988
+ fill Flush path to SVG [] []
989
+ ----
990
+
991
+ === Generated SVG
992
+
993
+ [source,svg]
994
+ ----
995
+ <path d="M 50 50 L 100 50 L 100 100 L 50 100 Z"
996
+ fill="#808080" stroke="none" />
997
+ ----
998
+
999
+ == Performance Characteristics
1000
+
1001
+ === Time Complexity
1002
+
1003
+ **Token Processing:** O(t) where t = number of tokens
1004
+
1005
+ **Operator Execution:** O(1) per operator (hash lookup + command execution)
1006
+
1007
+ **Data Structure Parsing:**
1008
+
1009
+ * Procedures: O(n) where n = tokens in procedure
1010
+ * Arrays: O(m) where m = elements in array
1011
+ * Dictionaries: O(k) where k = key-value pairs
1012
+
1013
+ **Overall:** O(t) linear in number of tokens
1014
+
1015
+ === Space Complexity
1016
+
1017
+ **Operand Stack:** O(s) where s = maximum stack depth (typically small)
1018
+
1019
+ **Dictionary Stack:** O(d) where d = number of definitions
1020
+
1021
+ **Graphics State Stack:** O(g) where g = `gsave` nesting depth
1022
+
1023
+ **SVG Output Buffer:** O(e) where e = number of SVG elements
1024
+
1025
+ **Overall:** O(t + e) dominated by SVG output size
1026
+
1027
+ === Optimization Opportunities
1028
+
1029
+ **Command Pooling:** Reuse command instances instead of creating new ones.
1030
+
1031
+ **Path Merging:** Combine consecutive path operations when possible.
1032
+
1033
+ **ClipPath Deduplication:** Already implemented - reuse identical clipPaths.
1034
+
1035
+ == Testing the Interpreter
1036
+
1037
+ === Unit Tests
1038
+
1039
+ **Stack Operations:**
1040
+
1041
+ [source,ruby]
1042
+ ----
1043
+ describe "stack operations" do
1044
+ it "pushes and pops values" do
1045
+ context = ExecutionContext.new
1046
+ context.push(10)
1047
+ context.push(20)
1048
+ expect(context.pop).to eq(20)
1049
+ expect(context.pop).to eq(10)
1050
+ end
1051
+ end
1052
+ ----
1053
+
1054
+ **Operator Execution:**
1055
+
1056
+ [source,ruby]
1057
+ ----
1058
+ describe "operator execution" do
1059
+ it "executes moveto command" do
1060
+ tokens = [
1061
+ Token(type: "number", value: "10"),
1062
+ Token(type: "number", value: "20"),
1063
+ Token(type: "operator", value: "moveto")
1064
+ ]
1065
+
1066
+ result = interpreter.interpret(tokens)
1067
+ expect(result[:svg]).to include("M 10 20")
1068
+ end
1069
+ end
1070
+ ----
1071
+
1072
+ **Dictionary Operations:**
1073
+
1074
+ [source,ruby]
1075
+ ----
1076
+ describe "dictionary operations" do
1077
+ it "defines and looks up values" do
1078
+ context = ExecutionContext.new
1079
+ context.define("myvar", 42)
1080
+ expect(context.lookup("myvar")).to eq(42)
1081
+ end
1082
+ end
1083
+ ----
1084
+
1085
+ === Integration Tests
1086
+
1087
+ **Complete Program:**
1088
+
1089
+ [source,ruby]
1090
+ ----
1091
+ describe "complete interpretation" do
1092
+ it "interprets rectangle drawing program" do
1093
+ ps = <<~PS
1094
+ newpath
1095
+ 0 0 moveto
1096
+ 100 0 lineto
1097
+ 100 100 lineto
1098
+ 0 100 lineto
1099
+ closepath
1100
+ fill
1101
+ PS
1102
+
1103
+ svg = Postsvg.convert(ps)
1104
+ expect(svg).to include('M 0 0 L 100 0 L 100 100 L 0 100 Z')
1105
+ expect(svg).to include('fill=')
1106
+ end
1107
+ end
1108
+ ----
1109
+
1110
+ == Next Steps
1111
+
1112
+ * Review link:generator-stage.adoc[Generator Stage] for SVG output details
1113
+ * Explore link:command-registry.adoc[Command Registry] for operator implementations
1114
+ * Study link:graphics-state-model.adoc[Graphics State Model] for state management
1115
+ * See link:../api-reference/interpreter.adoc[Interpreter API Reference] for usage
1116
+
1117
+ == Bibliography
1118
+
1119
+ * link:conversion-pipeline.adoc[Conversion Pipeline Documentation]
1120
+ * link:command-registry.adoc[Command Registry Architecture]
1121
+ * link:graphics-state-model.adoc[Graphics State Model]
1122
+ * link:../api-reference/interpreter.adoc[Interpreter API Reference]
1123
+ * link:../api-reference/execution-context.adoc[ExecutionContext API Reference]
1124
+ * PostScript Language Reference Manual, 3rd Edition (Adobe Systems)
1125
+ * Design Patterns: Elements of Reusable Object-Oriented Software