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,1210 @@
|
|
|
1
|
+
= ExecutionContext Class
|
|
2
|
+
:page-nav_order: 4
|
|
3
|
+
:page-parent: API Reference
|
|
4
|
+
|
|
5
|
+
== Purpose
|
|
6
|
+
|
|
7
|
+
The [`ExecutionContext`](../../lib/postsvg/execution_context.rb:9) class encapsulates all mutable state during PostScript execution, including the operand stack, dictionary stack, graphics state, and SVG output. It serves as the runtime environment for the interpreter.
|
|
8
|
+
|
|
9
|
+
== References
|
|
10
|
+
|
|
11
|
+
* link:../index.adoc[Documentation Home]
|
|
12
|
+
* link:../api-reference.adoc[API Reference Overview]
|
|
13
|
+
* link:interpreter.adoc[Interpreter Class]
|
|
14
|
+
* link:graphics-state.adoc[GraphicsState Class]
|
|
15
|
+
* link:../architecture.adoc[Architecture Overview]
|
|
16
|
+
|
|
17
|
+
== Concepts
|
|
18
|
+
|
|
19
|
+
**Operand Stack**:: A LIFO (Last-In-First-Out) stack holding operands for PostScript operators.
|
|
20
|
+
|
|
21
|
+
**Dictionary Stack**:: A stack of dictionaries providing scoped name-to-value mappings.
|
|
22
|
+
|
|
23
|
+
**Graphics State**:: The current drawing parameters including color, line width, transformation matrix, clip paths, etc.
|
|
24
|
+
|
|
25
|
+
**Graphics State Stack**:: A stack for saving and restoring graphics states via `gsave`/`grestore`.
|
|
26
|
+
|
|
27
|
+
**Current Transformation Matrix (CTM)**:: A 2D affine transformation matrix mapping user space to device space.
|
|
28
|
+
|
|
29
|
+
**Path Builder**:: An object that accumulates path commands (moveto, lineto, curveto) before rendering.
|
|
30
|
+
|
|
31
|
+
== Class Overview
|
|
32
|
+
|
|
33
|
+
The [`ExecutionContext`](../../lib/postsvg/execution_context.rb:9) class is defined in [`lib/postsvg/execution_context.rb`](../../lib/postsvg/execution_context.rb:1).
|
|
34
|
+
|
|
35
|
+
**Responsibilities:**
|
|
36
|
+
|
|
37
|
+
* Manage operand stack operations
|
|
38
|
+
* Maintain dictionary stack for name resolution
|
|
39
|
+
* Track graphics state and state stack
|
|
40
|
+
* Build and manage current path
|
|
41
|
+
* Generate SVG output elements
|
|
42
|
+
* Handle coordinate transformations
|
|
43
|
+
* Manage clip paths and patterns
|
|
44
|
+
* Provide utility functions for SVG generation
|
|
45
|
+
|
|
46
|
+
**Key Components:**
|
|
47
|
+
|
|
48
|
+
* Operand stack - General-purpose data stack
|
|
49
|
+
* Dictionary stack - Name/value lookup
|
|
50
|
+
* Graphics state - Drawing parameters
|
|
51
|
+
* Path builder - Path accumulation
|
|
52
|
+
* SVG output - Generated elements
|
|
53
|
+
|
|
54
|
+
**Design Pattern:**
|
|
55
|
+
|
|
56
|
+
The ExecutionContext follows the **Context Object** pattern, encapsulating all runtime state in a single mutable object that's passed to command implementations.
|
|
57
|
+
|
|
58
|
+
== Class Methods
|
|
59
|
+
|
|
60
|
+
=== new
|
|
61
|
+
|
|
62
|
+
Create a new ExecutionContext with initialized state.
|
|
63
|
+
|
|
64
|
+
**Syntax:**
|
|
65
|
+
|
|
66
|
+
[source,ruby]
|
|
67
|
+
----
|
|
68
|
+
context = Postsvg::ExecutionContext.new <1>
|
|
69
|
+
----
|
|
70
|
+
<1> Initialize context with default state
|
|
71
|
+
|
|
72
|
+
**Returns:**
|
|
73
|
+
|
|
74
|
+
New `ExecutionContext` instance with:
|
|
75
|
+
|
|
76
|
+
* Empty operand stack
|
|
77
|
+
* Global dictionary on dictionary stack
|
|
78
|
+
* Default graphics state
|
|
79
|
+
* Empty path
|
|
80
|
+
* Empty SVG output
|
|
81
|
+
|
|
82
|
+
**Source:**
|
|
83
|
+
|
|
84
|
+
[`lib/postsvg/execution_context.rb:14-30`](../../lib/postsvg/execution_context.rb:14)
|
|
85
|
+
|
|
86
|
+
**Initialization:**
|
|
87
|
+
|
|
88
|
+
[source,ruby]
|
|
89
|
+
----
|
|
90
|
+
# Operand stack
|
|
91
|
+
@stack = []
|
|
92
|
+
|
|
93
|
+
# Graphics state with defaults
|
|
94
|
+
@graphics_state = {
|
|
95
|
+
ctm: Matrix.new, # Identity matrix
|
|
96
|
+
fill: "black",
|
|
97
|
+
stroke: "black",
|
|
98
|
+
stroke_width: 1,
|
|
99
|
+
line_cap: "butt",
|
|
100
|
+
line_join: "miter",
|
|
101
|
+
font: "Arial, sans-serif",
|
|
102
|
+
font_size: 12,
|
|
103
|
+
clip_stack: [],
|
|
104
|
+
# ... other properties
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Dictionary stack with global dict
|
|
108
|
+
@dict_stack = [@global_dict]
|
|
109
|
+
|
|
110
|
+
# SVG output containers
|
|
111
|
+
@svg_output = { defs: [], paths: [], text: [] }
|
|
112
|
+
----
|
|
113
|
+
|
|
114
|
+
.Create execution context
|
|
115
|
+
[example]
|
|
116
|
+
====
|
|
117
|
+
[source,ruby]
|
|
118
|
+
----
|
|
119
|
+
require 'postsvg'
|
|
120
|
+
|
|
121
|
+
context = Postsvg::ExecutionContext.new
|
|
122
|
+
|
|
123
|
+
# Context is ready for use
|
|
124
|
+
puts "Stack size: #{context.stack.length}" # 0
|
|
125
|
+
puts "Fill color: #{context.graphics_state[:fill]}" # "black"
|
|
126
|
+
puts "Line width: #{context.graphics_state[:stroke_width]}" # 1
|
|
127
|
+
----
|
|
128
|
+
====
|
|
129
|
+
|
|
130
|
+
== Attributes
|
|
131
|
+
|
|
132
|
+
=== stack (read-only)
|
|
133
|
+
|
|
134
|
+
Access the operand stack.
|
|
135
|
+
|
|
136
|
+
**Returns:**
|
|
137
|
+
|
|
138
|
+
Array containing stack values (bottom to top)
|
|
139
|
+
|
|
140
|
+
**Source:**
|
|
141
|
+
|
|
142
|
+
[`lib/postsvg/execution_context.rb:10`](../../lib/postsvg/execution_context.rb:10)
|
|
143
|
+
|
|
144
|
+
.Inspect stack
|
|
145
|
+
[example]
|
|
146
|
+
====
|
|
147
|
+
[source,ruby]
|
|
148
|
+
----
|
|
149
|
+
context.push(10)
|
|
150
|
+
context.push(20)
|
|
151
|
+
context.push(30)
|
|
152
|
+
|
|
153
|
+
puts context.stack.inspect # [10, 20, 30]
|
|
154
|
+
puts "Stack depth: #{context.stack.length}" # 3
|
|
155
|
+
puts "Top element: #{context.stack.last}" # 30
|
|
156
|
+
----
|
|
157
|
+
====
|
|
158
|
+
|
|
159
|
+
=== graphics_state (read-only)
|
|
160
|
+
|
|
161
|
+
Access the current graphics state hash.
|
|
162
|
+
|
|
163
|
+
**Returns:**
|
|
164
|
+
|
|
165
|
+
Hash containing graphics state properties
|
|
166
|
+
|
|
167
|
+
**Source:**
|
|
168
|
+
|
|
169
|
+
[`lib/postsvg/execution_context.rb:10`](../../lib/postsvg/execution_context.rb:10)
|
|
170
|
+
|
|
171
|
+
**Graphics State Properties:**
|
|
172
|
+
|
|
173
|
+
[cols="1,2,1"]
|
|
174
|
+
|===
|
|
175
|
+
| Property | Description | Default
|
|
176
|
+
|
|
177
|
+
| `:ctm`
|
|
178
|
+
| Current transformation matrix
|
|
179
|
+
| `Matrix.new` (identity)
|
|
180
|
+
|
|
181
|
+
| `:fill`
|
|
182
|
+
| Fill color
|
|
183
|
+
| `"black"`
|
|
184
|
+
|
|
185
|
+
| `:stroke`
|
|
186
|
+
| Stroke color
|
|
187
|
+
| `"black"`
|
|
188
|
+
|
|
189
|
+
| `:stroke_width`
|
|
190
|
+
| Line width
|
|
191
|
+
| `1`
|
|
192
|
+
|
|
193
|
+
| `:line_cap`
|
|
194
|
+
| Line cap style
|
|
195
|
+
| `"butt"`
|
|
196
|
+
|
|
197
|
+
| `:line_join`
|
|
198
|
+
| Line join style
|
|
199
|
+
| `"miter"`
|
|
200
|
+
|
|
201
|
+
| `:font`
|
|
202
|
+
| Font family
|
|
203
|
+
| `"Arial, sans-serif"`
|
|
204
|
+
|
|
205
|
+
| `:font_size`
|
|
206
|
+
| Font size in points
|
|
207
|
+
| `12`
|
|
208
|
+
|
|
209
|
+
| `:clip_stack`
|
|
210
|
+
| Array of clip paths
|
|
211
|
+
| `[]`
|
|
212
|
+
|
|
213
|
+
| `:dash`
|
|
214
|
+
| Dash pattern
|
|
215
|
+
| `nil`
|
|
216
|
+
|
|
217
|
+
| `:pattern`
|
|
218
|
+
| Pattern reference
|
|
219
|
+
| `nil`
|
|
220
|
+
|
|
221
|
+
| `:blend_mode`
|
|
222
|
+
| Blend mode
|
|
223
|
+
| `:normal`
|
|
224
|
+
|
|
225
|
+
| `:opacity_alpha`
|
|
226
|
+
| Opacity value
|
|
227
|
+
| `1.0`
|
|
228
|
+
|===
|
|
229
|
+
|
|
230
|
+
.Access graphics state
|
|
231
|
+
[example]
|
|
232
|
+
====
|
|
233
|
+
[source,ruby]
|
|
234
|
+
----
|
|
235
|
+
context = Postsvg::ExecutionContext.new
|
|
236
|
+
|
|
237
|
+
# Read properties
|
|
238
|
+
puts context.graphics_state[:fill] # "black"
|
|
239
|
+
puts context.graphics_state[:stroke_width] # 1
|
|
240
|
+
|
|
241
|
+
# Modify properties
|
|
242
|
+
context.graphics_state[:fill] = "red"
|
|
243
|
+
context.graphics_state[:stroke_width] = 2.5
|
|
244
|
+
|
|
245
|
+
# Access transformation matrix
|
|
246
|
+
ctm = context.graphics_state[:ctm]
|
|
247
|
+
puts "Matrix: a=#{ctm.a}, e=#{ctm.e}, f=#{ctm.f}"
|
|
248
|
+
----
|
|
249
|
+
====
|
|
250
|
+
|
|
251
|
+
=== svg_output (read-only)
|
|
252
|
+
|
|
253
|
+
Access the SVG output container.
|
|
254
|
+
|
|
255
|
+
**Returns:**
|
|
256
|
+
|
|
257
|
+
Hash with three arrays:
|
|
258
|
+
|
|
259
|
+
* `:defs` - SVG definitions (patterns, gradients, clip paths)
|
|
260
|
+
* `:paths` - SVG path elements
|
|
261
|
+
* `:text` - SVG text elements
|
|
262
|
+
|
|
263
|
+
**Source:**
|
|
264
|
+
|
|
265
|
+
[`lib/postsvg/execution_context.rb:10`](../../lib/postsvg/execution_context.rb:10)
|
|
266
|
+
|
|
267
|
+
.Access SVG output
|
|
268
|
+
[example]
|
|
269
|
+
====
|
|
270
|
+
[source,ruby]
|
|
271
|
+
----
|
|
272
|
+
context = Postsvg::ExecutionContext.new
|
|
273
|
+
|
|
274
|
+
# Add elements during interpretation...
|
|
275
|
+
# (normally done by command implementations)
|
|
276
|
+
|
|
277
|
+
# Inspect results
|
|
278
|
+
output = context.svg_output
|
|
279
|
+
puts "Definitions: #{output[:defs].length}"
|
|
280
|
+
puts "Paths: #{output[:paths].length}"
|
|
281
|
+
puts "Text elements: #{output[:text].length}"
|
|
282
|
+
|
|
283
|
+
# Access individual elements
|
|
284
|
+
output[:paths].each_with_index do |path, i|
|
|
285
|
+
puts "Path #{i}: #{path[0..50]}..."
|
|
286
|
+
end
|
|
287
|
+
----
|
|
288
|
+
====
|
|
289
|
+
|
|
290
|
+
=== current_x, current_y (read/write)
|
|
291
|
+
|
|
292
|
+
Current point coordinates in user space.
|
|
293
|
+
|
|
294
|
+
**Returns:**
|
|
295
|
+
|
|
296
|
+
Numeric values representing current position
|
|
297
|
+
|
|
298
|
+
**Source:**
|
|
299
|
+
|
|
300
|
+
[`lib/postsvg/execution_context.rb:12`](../../lib/postsvg/execution_context.rb:12)
|
|
301
|
+
|
|
302
|
+
.Track current point
|
|
303
|
+
[example]
|
|
304
|
+
====
|
|
305
|
+
[source,ruby]
|
|
306
|
+
----
|
|
307
|
+
context = Postsvg::ExecutionContext.new
|
|
308
|
+
|
|
309
|
+
# Set current point
|
|
310
|
+
context.current_x = 100
|
|
311
|
+
context.current_y = 200
|
|
312
|
+
|
|
313
|
+
puts "Current point: (#{context.current_x}, #{context.current_y})"
|
|
314
|
+
|
|
315
|
+
# Update current point (usually done by path commands)
|
|
316
|
+
context.update_current_point(150, 250)
|
|
317
|
+
puts "New point: (#{context.current_x}, #{context.current_y})"
|
|
318
|
+
----
|
|
319
|
+
====
|
|
320
|
+
|
|
321
|
+
== Stack Operations
|
|
322
|
+
|
|
323
|
+
=== push
|
|
324
|
+
|
|
325
|
+
Push a value onto the operand stack.
|
|
326
|
+
|
|
327
|
+
**Syntax:**
|
|
328
|
+
|
|
329
|
+
[source,ruby]
|
|
330
|
+
----
|
|
331
|
+
context.push(value) <1>
|
|
332
|
+
----
|
|
333
|
+
<1> Push value onto top of stack
|
|
334
|
+
|
|
335
|
+
**Where:**
|
|
336
|
+
|
|
337
|
+
`value`:: Any Ruby object (Number, String, Array, Hash, etc.)
|
|
338
|
+
|
|
339
|
+
**Source:**
|
|
340
|
+
|
|
341
|
+
[`lib/postsvg/execution_context.rb:33-35`](../../lib/postsvg/execution_context.rb:33)
|
|
342
|
+
|
|
343
|
+
.Stack push examples
|
|
344
|
+
[example]
|
|
345
|
+
====
|
|
346
|
+
[source,ruby]
|
|
347
|
+
----
|
|
348
|
+
context = Postsvg::ExecutionContext.new
|
|
349
|
+
|
|
350
|
+
# Push numbers
|
|
351
|
+
context.push(42)
|
|
352
|
+
context.push(3.14)
|
|
353
|
+
|
|
354
|
+
# Push strings
|
|
355
|
+
context.push("hello")
|
|
356
|
+
|
|
357
|
+
# Push arrays
|
|
358
|
+
context.push([1, 2, 3])
|
|
359
|
+
|
|
360
|
+
# Push procedures
|
|
361
|
+
context.push({ type: "procedure", body: [...] })
|
|
362
|
+
|
|
363
|
+
puts context.stack.length # 5
|
|
364
|
+
----
|
|
365
|
+
====
|
|
366
|
+
|
|
367
|
+
=== pop
|
|
368
|
+
|
|
369
|
+
Pop and return the top value from the stack.
|
|
370
|
+
|
|
371
|
+
**Syntax:**
|
|
372
|
+
|
|
373
|
+
[source,ruby]
|
|
374
|
+
----
|
|
375
|
+
value = context.pop <1>
|
|
376
|
+
----
|
|
377
|
+
<1> Pop and return top value
|
|
378
|
+
|
|
379
|
+
**Returns:**
|
|
380
|
+
|
|
381
|
+
The top value from stack, or `nil` if stack is empty
|
|
382
|
+
|
|
383
|
+
**Source:**
|
|
384
|
+
|
|
385
|
+
[`lib/postsvg/execution_context.rb:37-39`](../../lib/postsvg/execution_context.rb:37)
|
|
386
|
+
|
|
387
|
+
.Stack pop examples
|
|
388
|
+
[example]
|
|
389
|
+
====
|
|
390
|
+
[source,ruby]
|
|
391
|
+
----
|
|
392
|
+
context.push(10)
|
|
393
|
+
context.push(20)
|
|
394
|
+
context.push(30)
|
|
395
|
+
|
|
396
|
+
val3 = context.pop # 30
|
|
397
|
+
val2 = context.pop # 20
|
|
398
|
+
val1 = context.pop # 10
|
|
399
|
+
|
|
400
|
+
puts "Popped: #{val3}, #{val2}, #{val1}"
|
|
401
|
+
puts "Stack empty: #{context.stack.empty?}" # true
|
|
402
|
+
----
|
|
403
|
+
====
|
|
404
|
+
|
|
405
|
+
=== pop_number
|
|
406
|
+
|
|
407
|
+
Pop a numeric value, with default fallback.
|
|
408
|
+
|
|
409
|
+
**Syntax:**
|
|
410
|
+
|
|
411
|
+
[source,ruby]
|
|
412
|
+
----
|
|
413
|
+
num = context.pop_number(default = 0) <1>
|
|
414
|
+
----
|
|
415
|
+
<1> Pop number with fallback to default
|
|
416
|
+
|
|
417
|
+
**Where:**
|
|
418
|
+
|
|
419
|
+
`default`:: (Optional) Value to return if pop fails or value is not numeric (default: 0)
|
|
420
|
+
|
|
421
|
+
**Returns:**
|
|
422
|
+
|
|
423
|
+
Numeric value or default
|
|
424
|
+
|
|
425
|
+
**Source:**
|
|
426
|
+
|
|
427
|
+
[`lib/postsvg/execution_context.rb:41-47`](../../lib/postsvg/execution_context.rb:41)
|
|
428
|
+
|
|
429
|
+
.Safe number popping
|
|
430
|
+
[example]
|
|
431
|
+
====
|
|
432
|
+
[source,ruby]
|
|
433
|
+
----
|
|
434
|
+
context.push(42)
|
|
435
|
+
context.push("not a number")
|
|
436
|
+
context.push(3.14)
|
|
437
|
+
|
|
438
|
+
num1 = context.pop_number # 3.14 (numeric)
|
|
439
|
+
num2 = context.pop_number # 0 (string, uses default)
|
|
440
|
+
num3 = context.pop_number(100) # 42 (numeric)
|
|
441
|
+
num4 = context.pop_number(100) # 100 (empty stack, uses default)
|
|
442
|
+
|
|
443
|
+
puts "Numbers: #{num1}, #{num2}, #{num3}, #{num4}"
|
|
444
|
+
----
|
|
445
|
+
====
|
|
446
|
+
|
|
447
|
+
=== peek
|
|
448
|
+
|
|
449
|
+
Look at the top value without removing it.
|
|
450
|
+
|
|
451
|
+
**Syntax:**
|
|
452
|
+
|
|
453
|
+
[source,ruby]
|
|
454
|
+
----
|
|
455
|
+
value = context.peek <1>
|
|
456
|
+
----
|
|
457
|
+
<1> Return top value without popping
|
|
458
|
+
|
|
459
|
+
**Returns:**
|
|
460
|
+
|
|
461
|
+
The top value from stack, or `nil` if stack is empty
|
|
462
|
+
|
|
463
|
+
**Source:**
|
|
464
|
+
|
|
465
|
+
[`lib/postsvg/execution_context.rb:49-51`](../../lib/postsvg/execution_context.rb:49)
|
|
466
|
+
|
|
467
|
+
.Peek at stack
|
|
468
|
+
[example]
|
|
469
|
+
====
|
|
470
|
+
[source,ruby]
|
|
471
|
+
----
|
|
472
|
+
context.push(100)
|
|
473
|
+
|
|
474
|
+
top = context.peek
|
|
475
|
+
puts "Top value: #{top}" # 100
|
|
476
|
+
puts "Stack size: #{context.stack.length}" # 1 (still there)
|
|
477
|
+
|
|
478
|
+
context.pop
|
|
479
|
+
puts "Stack size: #{context.stack.length}" # 0 (now removed)
|
|
480
|
+
----
|
|
481
|
+
====
|
|
482
|
+
|
|
483
|
+
== Graphics State Operations
|
|
484
|
+
|
|
485
|
+
=== save_graphics_state
|
|
486
|
+
|
|
487
|
+
Save current graphics state (gsave).
|
|
488
|
+
|
|
489
|
+
**Syntax:**
|
|
490
|
+
|
|
491
|
+
[source,ruby]
|
|
492
|
+
----
|
|
493
|
+
context.save_graphics_state <1>
|
|
494
|
+
----
|
|
495
|
+
<1> Push current state onto graphics state stack
|
|
496
|
+
|
|
497
|
+
**Source:**
|
|
498
|
+
|
|
499
|
+
[`lib/postsvg/execution_context.rb:54-66`](../../lib/postsvg/execution_context.rb:54)
|
|
500
|
+
|
|
501
|
+
**Saved State:**
|
|
502
|
+
|
|
503
|
+
* Complete graphics state (deep copy)
|
|
504
|
+
* Current point coordinates
|
|
505
|
+
* Current CTM
|
|
506
|
+
* Path builder state
|
|
507
|
+
|
|
508
|
+
.Save and modify state
|
|
509
|
+
[example]
|
|
510
|
+
====
|
|
511
|
+
[source,ruby]
|
|
512
|
+
----
|
|
513
|
+
context = Postsvg::ExecutionContext.new
|
|
514
|
+
|
|
515
|
+
# Set initial state
|
|
516
|
+
context.graphics_state[:fill] = "red"
|
|
517
|
+
context.graphics_state[:stroke_width] = 2
|
|
518
|
+
|
|
519
|
+
# Save state
|
|
520
|
+
context.save_graphics_state
|
|
521
|
+
|
|
522
|
+
# Modify state
|
|
523
|
+
context.graphics_state[:fill] = "blue"
|
|
524
|
+
context.graphics_state[:stroke_width] = 5
|
|
525
|
+
|
|
526
|
+
puts "Current fill: #{context.graphics_state[:fill]}" # "blue"
|
|
527
|
+
|
|
528
|
+
# Restore state
|
|
529
|
+
context.restore_graphics_state
|
|
530
|
+
|
|
531
|
+
puts "Restored fill: #{context.graphics_state[:fill]}" # "red"
|
|
532
|
+
puts "Restored width: #{context.graphics_state[:stroke_width]}" # 2
|
|
533
|
+
----
|
|
534
|
+
====
|
|
535
|
+
|
|
536
|
+
=== restore_graphics_state
|
|
537
|
+
|
|
538
|
+
Restore previously saved graphics state (grestore).
|
|
539
|
+
|
|
540
|
+
**Syntax:**
|
|
541
|
+
|
|
542
|
+
[source,ruby]
|
|
543
|
+
----
|
|
544
|
+
context.restore_graphics_state <1>
|
|
545
|
+
----
|
|
546
|
+
<1> Pop and restore state from graphics state stack
|
|
547
|
+
|
|
548
|
+
**Source:**
|
|
549
|
+
|
|
550
|
+
[`lib/postsvg/execution_context.rb:68-77`](../../lib/postsvg/execution_context.rb:68)
|
|
551
|
+
|
|
552
|
+
**Restored State:**
|
|
553
|
+
|
|
554
|
+
* Graphics state properties
|
|
555
|
+
* Current point
|
|
556
|
+
* CTM
|
|
557
|
+
* Path builder
|
|
558
|
+
|
|
559
|
+
.Nested save/restore
|
|
560
|
+
[example]
|
|
561
|
+
====
|
|
562
|
+
[source,ruby]
|
|
563
|
+
----
|
|
564
|
+
context = Postsvg::ExecutionContext.new
|
|
565
|
+
|
|
566
|
+
context.graphics_state[:fill] = "red"
|
|
567
|
+
context.save_graphics_state # Save "red"
|
|
568
|
+
|
|
569
|
+
context.graphics_state[:fill] = "green"
|
|
570
|
+
context.save_graphics_state # Save "green"
|
|
571
|
+
|
|
572
|
+
context.graphics_state[:fill] = "blue"
|
|
573
|
+
# Current: "blue"
|
|
574
|
+
|
|
575
|
+
context.restore_graphics_state
|
|
576
|
+
# Restored: "green"
|
|
577
|
+
|
|
578
|
+
context.restore_graphics_state
|
|
579
|
+
# Restored: "red"
|
|
580
|
+
|
|
581
|
+
puts context.graphics_state[:fill] # "red"
|
|
582
|
+
----
|
|
583
|
+
====
|
|
584
|
+
|
|
585
|
+
== Dictionary Operations
|
|
586
|
+
|
|
587
|
+
=== define
|
|
588
|
+
|
|
589
|
+
Define a name in the current dictionary.
|
|
590
|
+
|
|
591
|
+
**Syntax:**
|
|
592
|
+
|
|
593
|
+
[source,ruby]
|
|
594
|
+
----
|
|
595
|
+
context.define(key, value) <1>
|
|
596
|
+
----
|
|
597
|
+
<1> Store key-value pair in current dictionary
|
|
598
|
+
|
|
599
|
+
**Where:**
|
|
600
|
+
|
|
601
|
+
`key`:: Name to define (converted to string)
|
|
602
|
+
`value`:: Value to associate with name
|
|
603
|
+
|
|
604
|
+
**Source:**
|
|
605
|
+
|
|
606
|
+
[`lib/postsvg/execution_context.rb:92-94`](../../lib/postsvg/execution_context.rb:92)
|
|
607
|
+
|
|
608
|
+
.Define variables
|
|
609
|
+
[example]
|
|
610
|
+
====
|
|
611
|
+
[source,ruby]
|
|
612
|
+
----
|
|
613
|
+
context = Postsvg::ExecutionContext.new
|
|
614
|
+
|
|
615
|
+
# Define variables
|
|
616
|
+
context.define("x", 100)
|
|
617
|
+
context.define("y", 200)
|
|
618
|
+
context.define("radius", 50)
|
|
619
|
+
|
|
620
|
+
# Define procedures
|
|
621
|
+
context.define("myproc", {
|
|
622
|
+
type: "procedure",
|
|
623
|
+
body: [...]
|
|
624
|
+
})
|
|
625
|
+
|
|
626
|
+
# Look up later
|
|
627
|
+
x = context.lookup("x")
|
|
628
|
+
puts "x = #{x}" # 100
|
|
629
|
+
----
|
|
630
|
+
====
|
|
631
|
+
|
|
632
|
+
=== lookup
|
|
633
|
+
|
|
634
|
+
Look up a name in the dictionary stack.
|
|
635
|
+
|
|
636
|
+
**Syntax:**
|
|
637
|
+
|
|
638
|
+
[source,ruby]
|
|
639
|
+
----
|
|
640
|
+
value = context.lookup(name) <1>
|
|
641
|
+
----
|
|
642
|
+
<1> Search dictionary stack for name
|
|
643
|
+
|
|
644
|
+
**Where:**
|
|
645
|
+
|
|
646
|
+
`name`:: Name to look up (string)
|
|
647
|
+
|
|
648
|
+
**Returns:**
|
|
649
|
+
|
|
650
|
+
Value if found, `nil` otherwise
|
|
651
|
+
|
|
652
|
+
**Source:**
|
|
653
|
+
|
|
654
|
+
[`lib/postsvg/execution_context.rb:96-101`](../../lib/postsvg/execution_context.rb:96)
|
|
655
|
+
|
|
656
|
+
**Lookup Order:**
|
|
657
|
+
|
|
658
|
+
Searches from top to bottom of dictionary stack (most recent first).
|
|
659
|
+
|
|
660
|
+
.Dictionary lookup
|
|
661
|
+
[example]
|
|
662
|
+
====
|
|
663
|
+
[source,ruby]
|
|
664
|
+
----
|
|
665
|
+
context = Postsvg::ExecutionContext.new
|
|
666
|
+
|
|
667
|
+
# Define in global scope
|
|
668
|
+
context.define("global_var", 100)
|
|
669
|
+
|
|
670
|
+
# Create new dictionary scope
|
|
671
|
+
context.push_dict({})
|
|
672
|
+
context.define("local_var", 200)
|
|
673
|
+
|
|
674
|
+
# Both accessible
|
|
675
|
+
puts context.lookup("global_var") # 100
|
|
676
|
+
puts context.lookup("local_var") # 200
|
|
677
|
+
|
|
678
|
+
# Pop dictionary
|
|
679
|
+
context.pop_dict
|
|
680
|
+
|
|
681
|
+
# Only global accessible
|
|
682
|
+
puts context.lookup("global_var") # 100
|
|
683
|
+
puts context.lookup("local_var") # nil
|
|
684
|
+
----
|
|
685
|
+
====
|
|
686
|
+
|
|
687
|
+
=== push_dict
|
|
688
|
+
|
|
689
|
+
Push a new dictionary onto the dictionary stack.
|
|
690
|
+
|
|
691
|
+
**Syntax:**
|
|
692
|
+
|
|
693
|
+
[source,ruby]
|
|
694
|
+
----
|
|
695
|
+
context.push_dict(dict) <1>
|
|
696
|
+
----
|
|
697
|
+
<1> Add dictionary to stack
|
|
698
|
+
|
|
699
|
+
**Where:**
|
|
700
|
+
|
|
701
|
+
`dict`:: Hash to push (creates empty hash if not a Hash)
|
|
702
|
+
|
|
703
|
+
**Source:**
|
|
704
|
+
|
|
705
|
+
[`lib/postsvg/execution_context.rb:84-86`](../../lib/postsvg/execution_context.rb:84)
|
|
706
|
+
|
|
707
|
+
=== pop_dict
|
|
708
|
+
|
|
709
|
+
Pop a dictionary from the dictionary stack.
|
|
710
|
+
|
|
711
|
+
**Syntax:**
|
|
712
|
+
|
|
713
|
+
[source,ruby]
|
|
714
|
+
----
|
|
715
|
+
context.pop_dict <1>
|
|
716
|
+
----
|
|
717
|
+
<1> Remove top dictionary (preserves at least one)
|
|
718
|
+
|
|
719
|
+
**Source:**
|
|
720
|
+
|
|
721
|
+
[`lib/postsvg/execution_context.rb:88-90`](../../lib/postsvg/execution_context.rb:88)
|
|
722
|
+
|
|
723
|
+
== Path Operations
|
|
724
|
+
|
|
725
|
+
=== update_current_point
|
|
726
|
+
|
|
727
|
+
Update the current point coordinates.
|
|
728
|
+
|
|
729
|
+
**Syntax:**
|
|
730
|
+
|
|
731
|
+
[source,ruby]
|
|
732
|
+
----
|
|
733
|
+
context.update_current_point(x, y) <1>
|
|
734
|
+
----
|
|
735
|
+
<1> Set current point and last text position
|
|
736
|
+
|
|
737
|
+
**Where:**
|
|
738
|
+
|
|
739
|
+
`x`:: X coordinate
|
|
740
|
+
`y`:: Y coordinate
|
|
741
|
+
|
|
742
|
+
**Source:**
|
|
743
|
+
|
|
744
|
+
[`lib/postsvg/execution_context.rb:104-108`](../../lib/postsvg/execution_context.rb:104)
|
|
745
|
+
|
|
746
|
+
=== reset_path
|
|
747
|
+
|
|
748
|
+
Reset the path builder to empty state.
|
|
749
|
+
|
|
750
|
+
**Syntax:**
|
|
751
|
+
|
|
752
|
+
[source,ruby]
|
|
753
|
+
----
|
|
754
|
+
context.reset_path <1>
|
|
755
|
+
----
|
|
756
|
+
<1> Clear current path
|
|
757
|
+
|
|
758
|
+
**Source:**
|
|
759
|
+
|
|
760
|
+
[`lib/postsvg/execution_context.rb:110-112`](../../lib/postsvg/execution_context.rb:110)
|
|
761
|
+
|
|
762
|
+
=== new_path
|
|
763
|
+
|
|
764
|
+
Alias for reset_path (PostScript `newpath` operator).
|
|
765
|
+
|
|
766
|
+
**Syntax:**
|
|
767
|
+
|
|
768
|
+
[source,ruby]
|
|
769
|
+
----
|
|
770
|
+
context.new_path <1>
|
|
771
|
+
----
|
|
772
|
+
<1> Start new path
|
|
773
|
+
|
|
774
|
+
**Source:**
|
|
775
|
+
|
|
776
|
+
[`lib/postsvg/execution_context.rb:114-116`](../../lib/postsvg/execution_context.rb:114)
|
|
777
|
+
|
|
778
|
+
=== path_to_svg
|
|
779
|
+
|
|
780
|
+
Convert current path to SVG path data string.
|
|
781
|
+
|
|
782
|
+
**Syntax:**
|
|
783
|
+
|
|
784
|
+
[source,ruby]
|
|
785
|
+
----
|
|
786
|
+
path_data = context.path_to_svg <1>
|
|
787
|
+
----
|
|
788
|
+
<1> Generate SVG `d` attribute value
|
|
789
|
+
|
|
790
|
+
**Returns:**
|
|
791
|
+
|
|
792
|
+
String containing SVG path data (e.g., `"M 10,20 L 30,40"`)
|
|
793
|
+
|
|
794
|
+
**Source:**
|
|
795
|
+
|
|
796
|
+
[`lib/postsvg/execution_context.rb:122-124`](../../lib/postsvg/execution_context.rb:122)
|
|
797
|
+
|
|
798
|
+
.Path generation
|
|
799
|
+
[example]
|
|
800
|
+
====
|
|
801
|
+
[source,ruby]
|
|
802
|
+
----
|
|
803
|
+
context = Postsvg::ExecutionContext.new
|
|
804
|
+
|
|
805
|
+
# Build path using path_builder
|
|
806
|
+
context.path_builder.move_to(10, 20)
|
|
807
|
+
context.path_builder.line_to(30, 40)
|
|
808
|
+
context.path_builder.line_to(50, 30)
|
|
809
|
+
context.path_builder.close_path
|
|
810
|
+
|
|
811
|
+
# Convert to SVG
|
|
812
|
+
svg_path = context.path_to_svg
|
|
813
|
+
puts svg_path # "M 10,20 L 30,40 L 50,30 Z"
|
|
814
|
+
----
|
|
815
|
+
====
|
|
816
|
+
|
|
817
|
+
== SVG Output Operations
|
|
818
|
+
|
|
819
|
+
=== flush_path
|
|
820
|
+
|
|
821
|
+
Render current path to SVG and add to output.
|
|
822
|
+
|
|
823
|
+
**Syntax:**
|
|
824
|
+
|
|
825
|
+
[source,ruby]
|
|
826
|
+
----
|
|
827
|
+
context.flush_path(mode, fill_id = nil, bbox = nil) <1>
|
|
828
|
+
----
|
|
829
|
+
<1> Generate SVG path element and clear path
|
|
830
|
+
|
|
831
|
+
**Where:**
|
|
832
|
+
|
|
833
|
+
`mode`:: Hash specifying render mode
|
|
834
|
+
* `:fill` - Boolean, whether to fill
|
|
835
|
+
* `:stroke` - Boolean, whether to stroke
|
|
836
|
+
|
|
837
|
+
`fill_id`:: (Optional) Pattern or gradient ID for fill
|
|
838
|
+
|
|
839
|
+
`bbox`:: (Optional) Bounding box for clipping
|
|
840
|
+
|
|
841
|
+
**Source:**
|
|
842
|
+
|
|
843
|
+
[`lib/postsvg/execution_context.rb:305-320`](../../lib/postsvg/execution_context.rb:305)
|
|
844
|
+
|
|
845
|
+
.Flush path examples
|
|
846
|
+
[example]
|
|
847
|
+
====
|
|
848
|
+
[source,ruby]
|
|
849
|
+
----
|
|
850
|
+
# Build a path
|
|
851
|
+
context.path_builder.move_to(10, 10)
|
|
852
|
+
context.path_builder.line_to(90, 10)
|
|
853
|
+
context.path_builder.line_to(90, 90)
|
|
854
|
+
context.path_builder.close_path
|
|
855
|
+
|
|
856
|
+
# Render with fill only
|
|
857
|
+
context.flush_path({ fill: true, stroke: false })
|
|
858
|
+
|
|
859
|
+
# Result added to svg_output[:paths]
|
|
860
|
+
puts context.svg_output[:paths].last
|
|
861
|
+
# <path d="M 10,10 L 90,10 L 90,90 Z" fill="black" stroke="none" />
|
|
862
|
+
----
|
|
863
|
+
====
|
|
864
|
+
|
|
865
|
+
=== emit_svg_path
|
|
866
|
+
|
|
867
|
+
Generate SVG path element string.
|
|
868
|
+
|
|
869
|
+
**Syntax:**
|
|
870
|
+
|
|
871
|
+
[source,ruby]
|
|
872
|
+
----
|
|
873
|
+
path_element = context.emit_svg_path(d, mode, fill_id = nil, bbox = nil) <1>
|
|
874
|
+
----
|
|
875
|
+
<1> Create SVG path element with current graphics state
|
|
876
|
+
|
|
877
|
+
**Where:**
|
|
878
|
+
|
|
879
|
+
`d`:: SVG path data string
|
|
880
|
+
`mode`:: Hash with `:fill` and `:stroke` booleans
|
|
881
|
+
`fill_id`:: (Optional) Pattern/gradient reference
|
|
882
|
+
`bbox`:: (Optional) Bounding box
|
|
883
|
+
|
|
884
|
+
**Returns:**
|
|
885
|
+
|
|
886
|
+
String containing complete SVG path element
|
|
887
|
+
|
|
888
|
+
**Source:**
|
|
889
|
+
|
|
890
|
+
[`lib/postsvg/execution_context.rb:246-303`](../../lib/postsvg/execution_context.rb:246)
|
|
891
|
+
|
|
892
|
+
=== add_def
|
|
893
|
+
|
|
894
|
+
Add a definition element to SVG defs.
|
|
895
|
+
|
|
896
|
+
**Syntax:**
|
|
897
|
+
|
|
898
|
+
[source,ruby]
|
|
899
|
+
----
|
|
900
|
+
context.add_def(def_element) <1>
|
|
901
|
+
----
|
|
902
|
+
<1> Add to SVG definitions section
|
|
903
|
+
|
|
904
|
+
**Where:**
|
|
905
|
+
|
|
906
|
+
`def_element`:: String containing SVG definition (pattern, gradient, clipPath, etc.)
|
|
907
|
+
|
|
908
|
+
**Source:**
|
|
909
|
+
|
|
910
|
+
[`lib/postsvg/execution_context.rb:215-217`](../../lib/postsvg/execution_context.rb:215)
|
|
911
|
+
|
|
912
|
+
.Add definitions
|
|
913
|
+
[example]
|
|
914
|
+
====
|
|
915
|
+
[source,ruby]
|
|
916
|
+
----
|
|
917
|
+
context = Postsvg::ExecutionContext.new
|
|
918
|
+
|
|
919
|
+
# Add gradient definition
|
|
920
|
+
gradient = <<~SVG
|
|
921
|
+
<linearGradient id="grad1">
|
|
922
|
+
<stop offset="0%" stop-color="red"/>
|
|
923
|
+
<stop offset="100%" stop-color="blue"/>
|
|
924
|
+
</linearGradient>
|
|
925
|
+
SVG
|
|
926
|
+
|
|
927
|
+
context.add_def(gradient.strip)
|
|
928
|
+
|
|
929
|
+
# Add clip path
|
|
930
|
+
clip = '<clipPath id="clip1"><path d="..."/></clipPath>'
|
|
931
|
+
context.add_def(clip)
|
|
932
|
+
|
|
933
|
+
# Definitions will be included in final SVG
|
|
934
|
+
puts context.svg_output[:defs].length # 2
|
|
935
|
+
----
|
|
936
|
+
====
|
|
937
|
+
|
|
938
|
+
== Utility Methods
|
|
939
|
+
|
|
940
|
+
=== num_fmt
|
|
941
|
+
|
|
942
|
+
Format a number for SVG output.
|
|
943
|
+
|
|
944
|
+
**Syntax:**
|
|
945
|
+
|
|
946
|
+
[source,ruby]
|
|
947
|
+
----
|
|
948
|
+
formatted = context.num_fmt(number) <1>
|
|
949
|
+
----
|
|
950
|
+
<1> Format number to minimal string representation
|
|
951
|
+
|
|
952
|
+
**Where:**
|
|
953
|
+
|
|
954
|
+
`number`:: Numeric value to format
|
|
955
|
+
|
|
956
|
+
**Returns:**
|
|
957
|
+
|
|
958
|
+
String representation with:
|
|
959
|
+
|
|
960
|
+
* Integers as plain numbers: `10`
|
|
961
|
+
* Floats with up to 6 decimals, trailing zeros removed: `3.14`
|
|
962
|
+
* Special values as `"0"`: NaN, Infinity, nil
|
|
963
|
+
|
|
964
|
+
**Source:**
|
|
965
|
+
|
|
966
|
+
[`lib/postsvg/execution_context.rb:220-235`](../../lib/postsvg/execution_context.rb:220)
|
|
967
|
+
|
|
968
|
+
.Number formatting
|
|
969
|
+
[example]
|
|
970
|
+
====
|
|
971
|
+
[source,ruby]
|
|
972
|
+
----
|
|
973
|
+
context = Postsvg::ExecutionContext.new
|
|
974
|
+
|
|
975
|
+
puts context.num_fmt(10) # "10"
|
|
976
|
+
puts context.num_fmt(10.0) # "10"
|
|
977
|
+
puts context.num_fmt(10.5) # "10.5"
|
|
978
|
+
puts context.num_fmt(3.14159) # "3.14159"
|
|
979
|
+
puts context.num_fmt(1.00000) # "1"
|
|
980
|
+
puts context.num_fmt(0.1 + 0.2) # "0.3"
|
|
981
|
+
puts context.num_fmt(nil) # "0"
|
|
982
|
+
puts context.num_fmt(Float::NAN) # "0"
|
|
983
|
+
----
|
|
984
|
+
====
|
|
985
|
+
|
|
986
|
+
=== escape_xml
|
|
987
|
+
|
|
988
|
+
Escape string for XML/SVG output.
|
|
989
|
+
|
|
990
|
+
**Syntax:**
|
|
991
|
+
|
|
992
|
+
[source,ruby]
|
|
993
|
+
----
|
|
994
|
+
escaped = context.escape_xml(string) <1>
|
|
995
|
+
----
|
|
996
|
+
<1> Escape XML special characters
|
|
997
|
+
|
|
998
|
+
**Where:**
|
|
999
|
+
|
|
1000
|
+
`string`:: String to escape
|
|
1001
|
+
|
|
1002
|
+
**Returns:**
|
|
1003
|
+
|
|
1004
|
+
String with XML entities:
|
|
1005
|
+
|
|
1006
|
+
* `&` → `&`
|
|
1007
|
+
* `<` → `<`
|
|
1008
|
+
* `>` → `>`
|
|
1009
|
+
* `"` → `"`
|
|
1010
|
+
* `'` → `'`
|
|
1011
|
+
|
|
1012
|
+
**Source:**
|
|
1013
|
+
|
|
1014
|
+
[`lib/postsvg/execution_context.rb:237-244`](../../lib/postsvg/execution_context.rb:237)
|
|
1015
|
+
|
|
1016
|
+
.XML escaping
|
|
1017
|
+
[example]
|
|
1018
|
+
====
|
|
1019
|
+
[source,ruby]
|
|
1020
|
+
----
|
|
1021
|
+
context = Postsvg::ExecutionContext.new
|
|
1022
|
+
|
|
1023
|
+
text = "Text with <tags> & \"quotes\""
|
|
1024
|
+
escaped = context.escape_xml(text)
|
|
1025
|
+
|
|
1026
|
+
puts escaped
|
|
1027
|
+
# "Text with <tags> & "quotes""
|
|
1028
|
+
|
|
1029
|
+
# Safe for use in SVG
|
|
1030
|
+
svg_text = "<text>#{escaped}</text>"
|
|
1031
|
+
----
|
|
1032
|
+
====
|
|
1033
|
+
|
|
1034
|
+
== ID Generation
|
|
1035
|
+
|
|
1036
|
+
=== next_clippath_id
|
|
1037
|
+
|
|
1038
|
+
Generate unique clipPath ID.
|
|
1039
|
+
|
|
1040
|
+
**Syntax:**
|
|
1041
|
+
|
|
1042
|
+
[source,ruby]
|
|
1043
|
+
----
|
|
1044
|
+
id = context.next_clippath_id <1>
|
|
1045
|
+
----
|
|
1046
|
+
<1> Get next clipPath ID (auto-increments)
|
|
1047
|
+
|
|
1048
|
+
**Returns:**
|
|
1049
|
+
|
|
1050
|
+
Integer ID value
|
|
1051
|
+
|
|
1052
|
+
**Source:**
|
|
1053
|
+
|
|
1054
|
+
[`lib/postsvg/execution_context.rb:197-201`](../../lib/postsvg/execution_context.rb:197)
|
|
1055
|
+
|
|
1056
|
+
=== next_path_id
|
|
1057
|
+
|
|
1058
|
+
Generate unique path ID.
|
|
1059
|
+
|
|
1060
|
+
**Syntax:**
|
|
1061
|
+
|
|
1062
|
+
[source,ruby]
|
|
1063
|
+
----
|
|
1064
|
+
id = context.next_path_id <1>
|
|
1065
|
+
----
|
|
1066
|
+
<1> Get next path ID (auto-increments)
|
|
1067
|
+
|
|
1068
|
+
**Returns:**
|
|
1069
|
+
|
|
1070
|
+
Integer ID value
|
|
1071
|
+
|
|
1072
|
+
**Source:**
|
|
1073
|
+
|
|
1074
|
+
[`lib/postsvg/execution_context.rb:203-207`](../../lib/postsvg/execution_context.rb:203)
|
|
1075
|
+
|
|
1076
|
+
=== next_id
|
|
1077
|
+
|
|
1078
|
+
Generate generic unique ID.
|
|
1079
|
+
|
|
1080
|
+
**Syntax:**
|
|
1081
|
+
|
|
1082
|
+
[source,ruby]
|
|
1083
|
+
----
|
|
1084
|
+
id = context.next_id <1>
|
|
1085
|
+
----
|
|
1086
|
+
<1> Get next general-purpose ID (auto-increments)
|
|
1087
|
+
|
|
1088
|
+
**Returns:**
|
|
1089
|
+
|
|
1090
|
+
Integer ID value
|
|
1091
|
+
|
|
1092
|
+
**Source:**
|
|
1093
|
+
|
|
1094
|
+
[`lib/postsvg/execution_context.rb:209-213`](../../lib/postsvg/execution_context.rb:209)
|
|
1095
|
+
|
|
1096
|
+
.ID generation
|
|
1097
|
+
[example]
|
|
1098
|
+
====
|
|
1099
|
+
[source,ruby]
|
|
1100
|
+
----
|
|
1101
|
+
context = Postsvg::ExecutionContext.new
|
|
1102
|
+
|
|
1103
|
+
clip_id1 = context.next_clippath_id # 2
|
|
1104
|
+
clip_id2 = context.next_clippath_id # 4
|
|
1105
|
+
path_id1 = context.next_path_id # 2
|
|
1106
|
+
path_id2 = context.next_path_id # 4
|
|
1107
|
+
id1 = context.next_id # 0
|
|
1108
|
+
id2 = context.next_id # 1
|
|
1109
|
+
|
|
1110
|
+
# Use in SVG
|
|
1111
|
+
clip_def = "<clipPath id=\"clipPath#{clip_id1}\">...</clipPath>"
|
|
1112
|
+
path = "<path id=\"path#{path_id1}\" .../>"
|
|
1113
|
+
----
|
|
1114
|
+
====
|
|
1115
|
+
|
|
1116
|
+
## Thread Safety
|
|
1117
|
+
|
|
1118
|
+
The `ExecutionContext` class is **not thread-safe**. Each execution thread must have its own context instance.
|
|
1119
|
+
|
|
1120
|
+
.Multi-threaded usage
|
|
1121
|
+
[example]
|
|
1122
|
+
====
|
|
1123
|
+
[source,ruby]
|
|
1124
|
+
----
|
|
1125
|
+
# Bad: Sharing context
|
|
1126
|
+
context = Postsvg::ExecutionContext.new
|
|
1127
|
+
threads = 5.times.map do
|
|
1128
|
+
Thread.new { execute_with(context) } # NOT SAFE
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
# Good: Each thread has own context
|
|
1132
|
+
threads = 5.times.map do
|
|
1133
|
+
Thread.new do
|
|
1134
|
+
context = Postsvg::ExecutionContext.new
|
|
1135
|
+
execute_with(context)
|
|
1136
|
+
end
|
|
1137
|
+
end
|
|
1138
|
+
----
|
|
1139
|
+
====
|
|
1140
|
+
|
|
1141
|
+
== Best Practices
|
|
1142
|
+
|
|
1143
|
+
=== State Management
|
|
1144
|
+
|
|
1145
|
+
**Always save before modifying:**
|
|
1146
|
+
|
|
1147
|
+
[source,ruby]
|
|
1148
|
+
----
|
|
1149
|
+
# Save state
|
|
1150
|
+
context.save_graphics_state
|
|
1151
|
+
|
|
1152
|
+
# Modify
|
|
1153
|
+
context.graphics_state[:fill] = "red"
|
|
1154
|
+
# ... draw things ...
|
|
1155
|
+
|
|
1156
|
+
# Restore
|
|
1157
|
+
context.restore_graphics_state
|
|
1158
|
+
----
|
|
1159
|
+
|
|
1160
|
+
**Use dictionary scopes:**
|
|
1161
|
+
|
|
1162
|
+
[source,ruby]
|
|
1163
|
+
----
|
|
1164
|
+
# Create local scope
|
|
1165
|
+
context.push_dict({})
|
|
1166
|
+
context.define("local_var", value)
|
|
1167
|
+
|
|
1168
|
+
# Use local definitions
|
|
1169
|
+
# ...
|
|
1170
|
+
|
|
1171
|
+
# Clean up
|
|
1172
|
+
context.pop_dict
|
|
1173
|
+
----
|
|
1174
|
+
|
|
1175
|
+
=== Error Handling
|
|
1176
|
+
|
|
1177
|
+
**Check stack before popping:**
|
|
1178
|
+
|
|
1179
|
+
[source,ruby]
|
|
1180
|
+
----
|
|
1181
|
+
if context.stack.length >= 2
|
|
1182
|
+
y = context.pop
|
|
1183
|
+
x = context.pop
|
|
1184
|
+
else
|
|
1185
|
+
raise "Insufficient operands"
|
|
1186
|
+
end
|
|
1187
|
+
----
|
|
1188
|
+
|
|
1189
|
+
**Use pop_number for safety:**
|
|
1190
|
+
|
|
1191
|
+
[source,ruby]
|
|
1192
|
+
----
|
|
1193
|
+
# Safe number retrieval
|
|
1194
|
+
value = context.pop_number(0) # Default to 0 if not numeric
|
|
1195
|
+
----
|
|
1196
|
+
|
|
1197
|
+
== Next Steps
|
|
1198
|
+
|
|
1199
|
+
* Review link:interpreter.adoc[Interpreter Class] for execution flow
|
|
1200
|
+
* Learn about link:graphics-state.adoc[GraphicsState] for state details
|
|
1201
|
+
* See link:../architecture.adoc[Architecture] for system design
|
|
1202
|
+
* Check link:converter.adoc[Converter Class] for high-level API
|
|
1203
|
+
|
|
1204
|
+
== Bibliography
|
|
1205
|
+
|
|
1206
|
+
* link:interpreter.adoc[Interpreter Class Documentation]
|
|
1207
|
+
* link:graphics-state.adoc[GraphicsState Documentation]
|
|
1208
|
+
* link:converter.adoc[Converter Class Documentation]
|
|
1209
|
+
* link:../architecture.adoc[Architecture Overview]
|
|
1210
|
+
* link:https://www.adobe.com/jp/print/postscript/pdfs/PLRM.pdf[PostScript Language Reference Manual]
|