postsvg 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +105 -0
- data/CLAUDE.md +173 -0
- data/Gemfile +2 -3
- data/README.adoc +456 -179
- data/Rakefile +100 -0
- data/TODO.roadmap/00-architecture.md +139 -0
- data/TODO.roadmap/01-autoload-migration.md +39 -0
- data/TODO.roadmap/02-isolate-dormant-code.md +51 -0
- data/TODO.roadmap/03-domain-model.md +66 -0
- data/TODO.roadmap/04-lexer.md +40 -0
- data/TODO.roadmap/05-parser.md +43 -0
- data/TODO.roadmap/06-graphics-state.md +45 -0
- data/TODO.roadmap/07-matrix-and-color.md +37 -0
- data/TODO.roadmap/08-svg-builder.md +51 -0
- data/TODO.roadmap/09-renderer.md +52 -0
- data/TODO.roadmap/10-visitor.md +58 -0
- data/TODO.roadmap/11-operator-coverage.md +103 -0
- data/TODO.roadmap/12-svg-domain.md +62 -0
- data/TODO.roadmap/13-translation-handlers.md +69 -0
- data/TODO.roadmap/14-ps-serializer.md +49 -0
- data/TODO.roadmap/15-cli-and-public-api.md +47 -0
- data/TODO.roadmap/16-specs.md +84 -0
- data/TODO.roadmap/17-docs-sync.md +47 -0
- data/TODO.roadmap/18-performance-and-determinism.md +47 -0
- data/TODO.roadmap/19-error-model.md +45 -0
- data/TODO.roadmap/20-font-and-text.md +46 -0
- data/TODO.roadmap/21-images.md +45 -0
- data/TODO.roadmap/22-forms-and-resources.md +25 -0
- data/TODO.roadmap/23-level2-level3.md +52 -0
- data/TODO.roadmap/24-ci-and-release.md +42 -0
- data/TODO.roadmap/README.md +77 -0
- data/docs/.gitignore +29 -0
- data/docs/CHANGELOG.md +114 -0
- data/docs/COMPLETE_DOCUMENTATION_STATUS.md +376 -0
- data/docs/DEPLOYMENT.md +456 -0
- data/docs/DEPLOYMENT_INSTRUCTIONS.md +229 -0
- data/docs/DOCUMENTATION_PLAN.md +425 -0
- data/docs/FINAL_SUMMARY.md +657 -0
- data/docs/Gemfile +15 -0
- data/docs/README.md +327 -0
- data/docs/_config.yml +99 -0
- data/docs/advanced-topics.adoc +370 -0
- data/docs/api-reference/colors.adoc +705 -0
- data/docs/api-reference/converter.adoc +699 -0
- data/docs/api-reference/execution-context.adoc +1210 -0
- data/docs/api-reference/graphics-state.adoc +1070 -0
- data/docs/api-reference/interpreter.adoc +810 -0
- data/docs/api-reference/matrix.adoc +1179 -0
- data/docs/api-reference/path-builder.adoc +1284 -0
- data/docs/api-reference/postsvg-module.adoc +388 -0
- data/docs/api-reference/svg-generator.adoc +891 -0
- data/docs/api-reference/tokenizer.adoc +925 -0
- data/docs/api-reference.adoc +221 -0
- data/docs/architecture/command-registry.adoc +1191 -0
- data/docs/architecture/conversion-pipeline.adoc +746 -0
- data/docs/architecture/design-decisions.adoc +999 -0
- data/docs/architecture/generator-stage.adoc +1115 -0
- data/docs/architecture/graphics-state-model.adoc +1089 -0
- data/docs/architecture/interpreter-stage.adoc +1125 -0
- data/docs/architecture/parser-stage.adoc +1051 -0
- data/docs/architecture.adoc +354 -0
- data/docs/cli-reference/batch-command.adoc +616 -0
- data/docs/cli-reference/check-command.adoc +677 -0
- data/docs/cli-reference/cli-options.adoc +802 -0
- data/docs/cli-reference/convert-command.adoc +462 -0
- data/docs/cli-reference/version-command.adoc +296 -0
- data/docs/cli-reference.adoc +317 -0
- data/docs/concepts/conversion-pipeline.adoc +903 -0
- data/docs/concepts/coordinate-systems.adoc +836 -0
- data/docs/concepts/graphics-state.adoc +861 -0
- data/docs/concepts/path-operations.adoc +1076 -0
- data/docs/concepts/postscript-language.adoc +859 -0
- data/docs/concepts/svg-generation.adoc +937 -0
- data/docs/concepts.adoc +198 -0
- data/docs/contributing.adoc +443 -0
- data/docs/development.adoc +420 -0
- data/docs/faq.adoc +493 -0
- data/docs/getting-started/basic-usage.adoc +538 -0
- data/docs/getting-started/common-workflows.adoc +577 -0
- data/docs/getting-started/first-conversion.adoc +492 -0
- data/docs/getting-started/installation.adoc +534 -0
- data/docs/getting-started.adoc +94 -0
- data/docs/index.adoc +248 -0
- data/docs/optimization.adoc +196 -0
- data/docs/ps2svg_compatibility.adoc +149 -0
- data/docs/quick-reference.adoc +453 -0
- data/docs/sitemap.adoc +337 -0
- data/docs/troubleshooting.adoc +486 -0
- data/docs/validation.adoc +772 -0
- data/exe/postsvg +1 -0
- data/lib/postsvg/cli.rb +104 -57
- data/lib/postsvg/color.rb +132 -0
- data/lib/postsvg/errors.rb +68 -3
- data/lib/postsvg/format_number.rb +22 -0
- data/lib/postsvg/graphics_context.rb +80 -0
- data/lib/postsvg/graphics_stack.rb +43 -0
- data/lib/postsvg/model/literals/array.rb +41 -0
- data/lib/postsvg/model/literals/dictionary.rb +34 -0
- data/lib/postsvg/model/literals/hex.rb +37 -0
- data/lib/postsvg/model/literals/name.rb +40 -0
- data/lib/postsvg/model/literals/number.rb +36 -0
- data/lib/postsvg/model/literals/procedure.rb +41 -0
- data/lib/postsvg/model/literals/string.rb +30 -0
- data/lib/postsvg/model/literals.rb +19 -0
- data/lib/postsvg/model/operator.rb +58 -0
- data/lib/postsvg/model/operators/arithmetic.rb +264 -0
- data/lib/postsvg/model/operators/boolean.rb +182 -0
- data/lib/postsvg/model/operators/color.rb +74 -0
- data/lib/postsvg/model/operators/container.rb +186 -0
- data/lib/postsvg/model/operators/control_flow.rb +119 -0
- data/lib/postsvg/model/operators/device.rb +21 -0
- data/lib/postsvg/model/operators/dictionary.rb +118 -0
- data/lib/postsvg/model/operators/font.rb +121 -0
- data/lib/postsvg/model/operators/graphics_state.rb +84 -0
- data/lib/postsvg/model/operators/painting.rb +29 -0
- data/lib/postsvg/model/operators/path.rb +169 -0
- data/lib/postsvg/model/operators/stack.rb +72 -0
- data/lib/postsvg/model/operators/transformations.rb +103 -0
- data/lib/postsvg/model/operators.rb +89 -0
- data/lib/postsvg/model/program.rb +68 -0
- data/lib/postsvg/model/token.rb +43 -0
- data/lib/postsvg/model.rb +17 -0
- data/lib/postsvg/options.rb +29 -0
- data/lib/postsvg/renderer.rb +85 -0
- data/lib/postsvg/serializer.rb +325 -0
- data/lib/postsvg/source/ast_builder.rb +308 -0
- data/lib/postsvg/source/lexer.rb +322 -0
- data/lib/postsvg/source/operand_stack.rb +55 -0
- data/lib/postsvg/source.rb +21 -0
- data/lib/postsvg/svg/attribute_parser.rb +45 -0
- data/lib/postsvg/svg/clip_path_registry.rb +44 -0
- data/lib/postsvg/svg/document.rb +22 -0
- data/lib/postsvg/svg/element.rb +84 -0
- data/lib/postsvg/svg/elements/circle.rb +36 -0
- data/lib/postsvg/svg/elements/clip_path.rb +26 -0
- data/lib/postsvg/svg/elements/defs.rb +24 -0
- data/lib/postsvg/svg/elements/ellipse.rb +38 -0
- data/lib/postsvg/svg/elements/group.rb +37 -0
- data/lib/postsvg/svg/elements/image.rb +35 -0
- data/lib/postsvg/svg/elements/line.rb +36 -0
- data/lib/postsvg/svg/elements/path.rb +32 -0
- data/lib/postsvg/svg/elements/polygon.rb +12 -0
- data/lib/postsvg/svg/elements/polyline.rb +32 -0
- data/lib/postsvg/svg/elements/rect.rb +44 -0
- data/lib/postsvg/svg/elements/svg.rb +39 -0
- data/lib/postsvg/svg/elements/text.rb +42 -0
- data/lib/postsvg/svg/elements.rb +31 -0
- data/lib/postsvg/svg/paint.rb +34 -0
- data/lib/postsvg/svg/parser.rb +39 -0
- data/lib/postsvg/svg/path_data/command.rb +27 -0
- data/lib/postsvg/svg/path_data/parser.rb +82 -0
- data/lib/postsvg/svg/path_data.rb +17 -0
- data/lib/postsvg/svg/stroke.rb +31 -0
- data/lib/postsvg/svg/transform_list.rb +59 -0
- data/lib/postsvg/svg.rb +22 -0
- data/lib/postsvg/svg_builder.rb +249 -0
- data/lib/postsvg/translation/arc_converter.rb +86 -0
- data/lib/postsvg/translation/bounding_box.rb +59 -0
- data/lib/postsvg/translation/context.rb +34 -0
- data/lib/postsvg/translation/handler_registry.rb +38 -0
- data/lib/postsvg/translation/handlers/circle_handler.rb +28 -0
- data/lib/postsvg/translation/handlers/clip_path_handler.rb +13 -0
- data/lib/postsvg/translation/handlers/defs_handler.rb +15 -0
- data/lib/postsvg/translation/handlers/ellipse_handler.rb +31 -0
- data/lib/postsvg/translation/handlers/group_handler.rb +21 -0
- data/lib/postsvg/translation/handlers/image_handler.rb +20 -0
- data/lib/postsvg/translation/handlers/line_handler.rb +23 -0
- data/lib/postsvg/translation/handlers/open_handler.rb +18 -0
- data/lib/postsvg/translation/handlers/path_handler.rb +356 -0
- data/lib/postsvg/translation/handlers/polygon_handler.rb +33 -0
- data/lib/postsvg/translation/handlers/polyline_handler.rb +31 -0
- data/lib/postsvg/translation/handlers/rect_handler.rb +27 -0
- data/lib/postsvg/translation/handlers/shared.rb +110 -0
- data/lib/postsvg/translation/handlers/svg_handler.rb +25 -0
- data/lib/postsvg/translation/handlers/text_handler.rb +56 -0
- data/lib/postsvg/translation/handlers.rb +25 -0
- data/lib/postsvg/translation/ps_renderer.rb +105 -0
- data/lib/postsvg/translation/record_emitter.rb +35 -0
- data/lib/postsvg/translation.rb +17 -0
- data/lib/postsvg/version.rb +1 -1
- data/lib/postsvg/visitors/ps_visitor/arithmetic.rb +125 -0
- data/lib/postsvg/visitors/ps_visitor/boolean.rb +105 -0
- data/lib/postsvg/visitors/ps_visitor/color.rb +53 -0
- data/lib/postsvg/visitors/ps_visitor/common.rb +66 -0
- data/lib/postsvg/visitors/ps_visitor/container.rb +164 -0
- data/lib/postsvg/visitors/ps_visitor/control_flow.rb +110 -0
- data/lib/postsvg/visitors/ps_visitor/device.rb +20 -0
- data/lib/postsvg/visitors/ps_visitor/dictionary.rb +89 -0
- data/lib/postsvg/visitors/ps_visitor/font.rb +93 -0
- data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +55 -0
- data/lib/postsvg/visitors/ps_visitor/painting.rb +90 -0
- data/lib/postsvg/visitors/ps_visitor/path.rb +112 -0
- data/lib/postsvg/visitors/ps_visitor/stack.rb +47 -0
- data/lib/postsvg/visitors/ps_visitor/transformations.rb +101 -0
- data/lib/postsvg/visitors/ps_visitor.rb +208 -0
- data/lib/postsvg/visitors.rb +9 -0
- data/lib/postsvg.rb +93 -59
- data/lychee.toml +86 -0
- metadata +216 -11
- data/postsvg.gemspec +0 -38
|
@@ -0,0 +1,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]
|