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,999 @@
|
|
|
1
|
+
= Design Decisions
|
|
2
|
+
:page-nav_order: 7
|
|
3
|
+
|
|
4
|
+
== Purpose
|
|
5
|
+
|
|
6
|
+
This document describes the key architectural decisions made in Postsvg, the rationale behind them, alternatives considered, and trade-offs accepted. Understanding these decisions helps developers comprehend why the system is structured as it is and guides future development decisions.
|
|
7
|
+
|
|
8
|
+
== References
|
|
9
|
+
|
|
10
|
+
* link:../architecture.adoc[Architecture Overview]
|
|
11
|
+
* link:conversion-pipeline.adoc[Conversion Pipeline]
|
|
12
|
+
* link:command-registry.adoc[Command Registry]
|
|
13
|
+
* link:graphics-state-model.adoc[Graphics State Model]
|
|
14
|
+
|
|
15
|
+
== Concepts
|
|
16
|
+
|
|
17
|
+
**Architectural Decision Record (ADR)**:: Documentation of significant architectural choices.
|
|
18
|
+
|
|
19
|
+
**Trade-off Analysis**:: Balancing competing concerns (performance, simplicity, correctness).
|
|
20
|
+
|
|
21
|
+
**Design Pattern Selection**:: Choosing appropriate patterns for the problem domain.
|
|
22
|
+
|
|
23
|
+
**Technology Choices**:: Selecting libraries, frameworks, and approaches.
|
|
24
|
+
|
|
25
|
+
== Decision 1: Three-Stage Pipeline Architecture
|
|
26
|
+
|
|
27
|
+
=== Context
|
|
28
|
+
|
|
29
|
+
PostScript to SVG conversion requires multiple distinct transformations:
|
|
30
|
+
|
|
31
|
+
* Text → Tokens (lexical analysis)
|
|
32
|
+
* Tokens → Execution (semantic interpretation)
|
|
33
|
+
* Execution → SVG (output generation)
|
|
34
|
+
|
|
35
|
+
=== Decision
|
|
36
|
+
|
|
37
|
+
Implement a three-stage pipeline with clear separation between stages:
|
|
38
|
+
|
|
39
|
+
1. **Tokenization:** Lexical analysis only
|
|
40
|
+
2. **Interpretation:** Execute PostScript semantics
|
|
41
|
+
3. **Generation:** Produce SVG output
|
|
42
|
+
|
|
43
|
+
=== Rationale
|
|
44
|
+
|
|
45
|
+
**Benefits:**
|
|
46
|
+
|
|
47
|
+
* **Separation of Concerns:** Each stage has single responsibility
|
|
48
|
+
* **Testability:** Stages can be tested independently
|
|
49
|
+
* **Maintainability:** Changes to one stage don't affect others
|
|
50
|
+
* **Clarity:** Data flow is explicit and unidirectional
|
|
51
|
+
|
|
52
|
+
**Code Location:** See link:conversion-pipeline.adoc[Conversion Pipeline Documentation]
|
|
53
|
+
|
|
54
|
+
=== Alternatives Considered
|
|
55
|
+
|
|
56
|
+
**Single-Pass Conversion:**
|
|
57
|
+
|
|
58
|
+
* Parse and generate SVG simultaneously
|
|
59
|
+
* **Rejected:** Too complex, hard to test, mixing concerns
|
|
60
|
+
|
|
61
|
+
**AST-Based Approach:**
|
|
62
|
+
|
|
63
|
+
* Build full abstract syntax tree before execution
|
|
64
|
+
* **Rejected:** Unnecessary memory overhead, PostScript is procedural
|
|
65
|
+
|
|
66
|
+
**Streaming Pipeline:**
|
|
67
|
+
|
|
68
|
+
* Process tokens as they're produced
|
|
69
|
+
* **Deferred:** Current approach is simpler, streaming could be added later
|
|
70
|
+
|
|
71
|
+
=== Trade-offs
|
|
72
|
+
|
|
73
|
+
**Accepted:**
|
|
74
|
+
|
|
75
|
+
* Multiple passes over data (not streaming)
|
|
76
|
+
* Token array kept in memory
|
|
77
|
+
|
|
78
|
+
**Gained:**
|
|
79
|
+
|
|
80
|
+
* Simple, understandable architecture
|
|
81
|
+
* Easy testing and debugging
|
|
82
|
+
* Clear error boundaries
|
|
83
|
+
|
|
84
|
+
== Decision 2: Command Pattern for Operators
|
|
85
|
+
|
|
86
|
+
=== Context
|
|
87
|
+
|
|
88
|
+
PostScript has 300+ operators with different behaviors. Need extensible way to implement operators.
|
|
89
|
+
|
|
90
|
+
=== Decision
|
|
91
|
+
|
|
92
|
+
Use Command Pattern with:
|
|
93
|
+
|
|
94
|
+
* Base command class defining interface
|
|
95
|
+
* One class per operator
|
|
96
|
+
* Central registry for lookup
|
|
97
|
+
* Lazy instantiation
|
|
98
|
+
|
|
99
|
+
**Code Location:** See link:command-registry.adoc[Command Registry Documentation]
|
|
100
|
+
|
|
101
|
+
=== Rationale
|
|
102
|
+
|
|
103
|
+
**Benefits:**
|
|
104
|
+
|
|
105
|
+
* **Extensibility:** Easy to add new operators
|
|
106
|
+
* **Testability:** Each operator tested in isolation
|
|
107
|
+
* **Maintainability:** Changes to one operator don't affect others
|
|
108
|
+
* **Flexibility:** Operators can be overridden or aliased
|
|
109
|
+
|
|
110
|
+
**Example:**
|
|
111
|
+
|
|
112
|
+
[source,ruby]
|
|
113
|
+
----
|
|
114
|
+
class MoveTo < Base
|
|
115
|
+
def call(context)
|
|
116
|
+
y = context.pop_number
|
|
117
|
+
x = context.pop_number
|
|
118
|
+
context.path_builder.move_to(x, y)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
----
|
|
122
|
+
|
|
123
|
+
=== Alternatives Considered
|
|
124
|
+
|
|
125
|
+
**Giant Switch Statement:**
|
|
126
|
+
|
|
127
|
+
[source,ruby]
|
|
128
|
+
----
|
|
129
|
+
case operator
|
|
130
|
+
when "moveto"
|
|
131
|
+
# inline implementation
|
|
132
|
+
when "lineto"
|
|
133
|
+
# inline implementation
|
|
134
|
+
# ... 300+ cases
|
|
135
|
+
end
|
|
136
|
+
----
|
|
137
|
+
|
|
138
|
+
**Rejected:** Unmaintainable, hard to test, poor separation
|
|
139
|
+
|
|
140
|
+
**Method Per Operator:**
|
|
141
|
+
|
|
142
|
+
[source,ruby]
|
|
143
|
+
----
|
|
144
|
+
def execute_moveto
|
|
145
|
+
# implementation
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def execute_lineto
|
|
149
|
+
# implementation
|
|
150
|
+
end
|
|
151
|
+
----
|
|
152
|
+
|
|
153
|
+
**Rejected:** No polymorphism, hard to organize, testing harder
|
|
154
|
+
|
|
155
|
+
**External DSL:**
|
|
156
|
+
|
|
157
|
+
* Define operators in external configuration
|
|
158
|
+
* **Rejected:** Too complex, Ruby is expressive enough
|
|
159
|
+
|
|
160
|
+
=== Trade-offs
|
|
161
|
+
|
|
162
|
+
**Accepted:**
|
|
163
|
+
|
|
164
|
+
* One file per operator (many files)
|
|
165
|
+
* Instantiation overhead per call
|
|
166
|
+
|
|
167
|
+
**Gained:**
|
|
168
|
+
|
|
169
|
+
* Clean, testable code
|
|
170
|
+
* Easy to extend
|
|
171
|
+
* Clear organization
|
|
172
|
+
|
|
173
|
+
== Decision 3: Integrated SVG Generation
|
|
174
|
+
|
|
175
|
+
=== Context
|
|
176
|
+
|
|
177
|
+
SVG output could be generated as separate post-processing step or during interpretation.
|
|
178
|
+
|
|
179
|
+
=== Decision
|
|
180
|
+
|
|
181
|
+
Generate SVG incrementally during interpretation:
|
|
182
|
+
|
|
183
|
+
* Path elements created when `stroke`/`fill` executed
|
|
184
|
+
* SVG buffer accumulates during execution
|
|
185
|
+
* Final assembly at end
|
|
186
|
+
|
|
187
|
+
**Code Location:** [`lib/postsvg/interpreter.rb:219`](../../lib/postsvg/interpreter.rb:219)
|
|
188
|
+
|
|
189
|
+
=== Rationale
|
|
190
|
+
|
|
191
|
+
**Benefits:**
|
|
192
|
+
|
|
193
|
+
* **Efficiency:** Single pass, no separate traversal
|
|
194
|
+
* **Simplicity:** No intermediate representation needed
|
|
195
|
+
* **Memory:** Output streamed to buffer, not stored
|
|
196
|
+
* **Correctness:** Graphics state available when generating output
|
|
197
|
+
|
|
198
|
+
=== Alternatives Considered
|
|
199
|
+
|
|
200
|
+
**Separate Generator Pass:**
|
|
201
|
+
|
|
202
|
+
[source,ruby]
|
|
203
|
+
----
|
|
204
|
+
# After interpretation:
|
|
205
|
+
intermediate = interpreter.result
|
|
206
|
+
svg_generator.generate(intermediate)
|
|
207
|
+
----
|
|
208
|
+
|
|
209
|
+
**Rejected:** Requires storing all operations, extra complexity
|
|
210
|
+
|
|
211
|
+
**Deferred Generation:**
|
|
212
|
+
|
|
213
|
+
* Store operation log, generate on demand
|
|
214
|
+
* **Rejected:** Memory overhead, delayed errors
|
|
215
|
+
|
|
216
|
+
=== Trade-offs
|
|
217
|
+
|
|
218
|
+
**Accepted:**
|
|
219
|
+
|
|
220
|
+
* Interpreter and generator coupled
|
|
221
|
+
* Harder to swap output formats
|
|
222
|
+
|
|
223
|
+
**Gained:**
|
|
224
|
+
|
|
225
|
+
* Simple, efficient implementation
|
|
226
|
+
* Immediate error detection
|
|
227
|
+
* Lower memory usage
|
|
228
|
+
|
|
229
|
+
== Decision 4: Immutable Graphics State with Cloning
|
|
230
|
+
|
|
231
|
+
=== Context
|
|
232
|
+
|
|
233
|
+
Graphics state must be saved/restored with `gsave`/`grestore`. Need safe way to manage state.
|
|
234
|
+
|
|
235
|
+
=== Decision
|
|
236
|
+
|
|
237
|
+
Clone graphics state on save:
|
|
238
|
+
|
|
239
|
+
[source,ruby]
|
|
240
|
+
----
|
|
241
|
+
def save_graphics_state
|
|
242
|
+
@g_stack << {
|
|
243
|
+
graphics_state: clone_graphics_state(@graphics_state),
|
|
244
|
+
current_x: @current_x,
|
|
245
|
+
current_y: @current_y,
|
|
246
|
+
path_builder: @path_builder.dup
|
|
247
|
+
}
|
|
248
|
+
end
|
|
249
|
+
----
|
|
250
|
+
|
|
251
|
+
**Code Location:** See link:graphics-state-model.adoc[Graphics State Model Documentation]
|
|
252
|
+
|
|
253
|
+
=== Rationale
|
|
254
|
+
|
|
255
|
+
**Benefits:**
|
|
256
|
+
|
|
257
|
+
* **Correctness:** No accidental mutations
|
|
258
|
+
* **Debugging:** State history preserved
|
|
259
|
+
* **Predictability:** Restore always works correctly
|
|
260
|
+
* **Thread Safety:** Potential for future parallelization
|
|
261
|
+
|
|
262
|
+
=== Alternatives Considered
|
|
263
|
+
|
|
264
|
+
**Mutable State with Careful Copying:**
|
|
265
|
+
|
|
266
|
+
* Track which fields changed
|
|
267
|
+
* Only save/restore changed fields
|
|
268
|
+
* **Rejected:** Error-prone, hard to maintain
|
|
269
|
+
|
|
270
|
+
**Persistent Data Structures:**
|
|
271
|
+
|
|
272
|
+
* Use immutable data structure library
|
|
273
|
+
* **Rejected:** Overkill, performance overhead
|
|
274
|
+
|
|
275
|
+
**Copy-on-Write:**
|
|
276
|
+
|
|
277
|
+
* Share state until modification
|
|
278
|
+
* **Deferred:** Current approach is simpler, could optimize later
|
|
279
|
+
|
|
280
|
+
=== Trade-offs
|
|
281
|
+
|
|
282
|
+
**Accepted:**
|
|
283
|
+
|
|
284
|
+
* Memory overhead from cloning
|
|
285
|
+
* CPU overhead from copying
|
|
286
|
+
|
|
287
|
+
**Gained:**
|
|
288
|
+
|
|
289
|
+
* Correctness and safety
|
|
290
|
+
* Simple implementation
|
|
291
|
+
* Easy debugging
|
|
292
|
+
|
|
293
|
+
== Decision 5: Lazy Command Instantiation
|
|
294
|
+
|
|
295
|
+
=== Context
|
|
296
|
+
|
|
297
|
+
Registry could create all command instances upfront or on-demand.
|
|
298
|
+
|
|
299
|
+
=== Decision
|
|
300
|
+
|
|
301
|
+
Create command instances on each lookup:
|
|
302
|
+
|
|
303
|
+
[source,ruby]
|
|
304
|
+
----
|
|
305
|
+
def get(operator_name)
|
|
306
|
+
command_class = @commands[operator_name]
|
|
307
|
+
command_class&.new # New instance each time
|
|
308
|
+
end
|
|
309
|
+
----
|
|
310
|
+
|
|
311
|
+
=== Rationale
|
|
312
|
+
|
|
313
|
+
**Benefits:**
|
|
314
|
+
|
|
315
|
+
* **Memory Efficiency:** Only create what's used
|
|
316
|
+
* **Thread Safety:** Each execution gets fresh instance
|
|
317
|
+
* **Simplicity:** No instance management needed
|
|
318
|
+
|
|
319
|
+
**Measurement:**
|
|
320
|
+
|
|
321
|
+
* Typical program uses ~30 unique operators
|
|
322
|
+
* Creating 30 instances is negligible overhead
|
|
323
|
+
|
|
324
|
+
=== Alternatives Considered
|
|
325
|
+
|
|
326
|
+
**Instance Pooling:**
|
|
327
|
+
|
|
328
|
+
[source,ruby]
|
|
329
|
+
----
|
|
330
|
+
def get(operator_name)
|
|
331
|
+
@instances[operator_name] ||= @commands[operator_name]&.new
|
|
332
|
+
end
|
|
333
|
+
----
|
|
334
|
+
|
|
335
|
+
**Rejected:** Minimal performance gain, harder to reason about
|
|
336
|
+
|
|
337
|
+
**Singleton Instances:**
|
|
338
|
+
|
|
339
|
+
* One instance per operator class
|
|
340
|
+
* **Rejected:** Thread-safety concerns, no real benefit
|
|
341
|
+
|
|
342
|
+
=== Trade-offs
|
|
343
|
+
|
|
344
|
+
**Accepted:**
|
|
345
|
+
|
|
346
|
+
* Small instantiation overhead per operator call
|
|
347
|
+
|
|
348
|
+
**Gained:**
|
|
349
|
+
|
|
350
|
+
* Simple, safe implementation
|
|
351
|
+
* Lower memory baseline
|
|
352
|
+
* Thread-safe by default
|
|
353
|
+
|
|
354
|
+
== Decision 6: Number Formatting and Optimization
|
|
355
|
+
|
|
356
|
+
=== Context
|
|
357
|
+
|
|
358
|
+
SVG numbers can be verbose (`10.000000`). Need balance between precision and size.
|
|
359
|
+
|
|
360
|
+
=== Decision
|
|
361
|
+
|
|
362
|
+
Format numbers with smart optimization:
|
|
363
|
+
|
|
364
|
+
[source,ruby]
|
|
365
|
+
----
|
|
366
|
+
def num_fmt(n)
|
|
367
|
+
return "0" if n.nil? || n.nan? || n.infinite?
|
|
368
|
+
|
|
369
|
+
n = n.to_f
|
|
370
|
+
|
|
371
|
+
if (n - n.round).abs < 1e-10
|
|
372
|
+
n.round.to_i.to_s # "10" not "10.0"
|
|
373
|
+
else
|
|
374
|
+
format("%.3f", n).sub(/\.?0+$/, "") # "10.5" not "10.500"
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
----
|
|
378
|
+
|
|
379
|
+
=== Rationale
|
|
380
|
+
|
|
381
|
+
**Benefits:**
|
|
382
|
+
|
|
383
|
+
* **File Size:** 20-40% reduction in typical output
|
|
384
|
+
* **Readability:** Cleaner, more readable SVG
|
|
385
|
+
* **Precision:** 3 decimals sufficient for most graphics
|
|
386
|
+
* **Correctness:** Handles edge cases (NaN, Infinity)
|
|
387
|
+
|
|
388
|
+
**Example:**
|
|
389
|
+
|
|
390
|
+
[source]
|
|
391
|
+
----
|
|
392
|
+
10.000000 → 10 (6 bytes saved)
|
|
393
|
+
10.500000 → 10.5 (5 bytes saved)
|
|
394
|
+
0.0001 → 0 (edge case handled)
|
|
395
|
+
----
|
|
396
|
+
|
|
397
|
+
=== Alternatives Considered
|
|
398
|
+
|
|
399
|
+
**No Formatting:**
|
|
400
|
+
|
|
401
|
+
* Use Ruby's default `to_s`
|
|
402
|
+
* **Rejected:** Verbose output, larger files
|
|
403
|
+
|
|
404
|
+
**Fixed Precision:**
|
|
405
|
+
|
|
406
|
+
* Always use `.2f` or `.6f`
|
|
407
|
+
* **Rejected:** Either too little or too much precision
|
|
408
|
+
|
|
409
|
+
**Configurable Precision:**
|
|
410
|
+
|
|
411
|
+
* Let user specify precision
|
|
412
|
+
* **Deferred:** Current default works well, could add option later
|
|
413
|
+
|
|
414
|
+
=== Trade-offs
|
|
415
|
+
|
|
416
|
+
**Accepted:**
|
|
417
|
+
|
|
418
|
+
* Small formatting overhead per number
|
|
419
|
+
* Fixed precision (3 decimals)
|
|
420
|
+
|
|
421
|
+
**Gained:**
|
|
422
|
+
|
|
423
|
+
* Significantly smaller output
|
|
424
|
+
* Readable SVG
|
|
425
|
+
* Handles edge cases
|
|
426
|
+
|
|
427
|
+
== Decision 7: ClipPath Deduplication
|
|
428
|
+
|
|
429
|
+
=== Context
|
|
430
|
+
|
|
431
|
+
Multiple identical clipping regions generate duplicate `<clipPath>` definitions.
|
|
432
|
+
|
|
433
|
+
=== Decision
|
|
434
|
+
|
|
435
|
+
Cache clipPath definitions by path data:
|
|
436
|
+
|
|
437
|
+
[source,ruby]
|
|
438
|
+
----
|
|
439
|
+
@clippath_cache = {} # path_data => clip_id
|
|
440
|
+
|
|
441
|
+
clip_id = @clippath_cache[clip_path_d]
|
|
442
|
+
unless clip_id
|
|
443
|
+
clip_id = next_clippath_id
|
|
444
|
+
@clippath_cache[clip_path_d] = clip_id
|
|
445
|
+
# Create <clipPath> definition
|
|
446
|
+
end
|
|
447
|
+
----
|
|
448
|
+
|
|
449
|
+
=== Rationale
|
|
450
|
+
|
|
451
|
+
**Benefits:**
|
|
452
|
+
|
|
453
|
+
* **File Size:** Eliminates duplicate definitions
|
|
454
|
+
* **Performance:** Fewer DOM elements in browser
|
|
455
|
+
* **Correctness:** Semantically equivalent to duplicates
|
|
456
|
+
|
|
457
|
+
**Measurement:**
|
|
458
|
+
|
|
459
|
+
* Complex documents may have 50+ identical clips
|
|
460
|
+
* Deduplication can reduce file size by 80%
|
|
461
|
+
|
|
462
|
+
=== Alternatives Considered
|
|
463
|
+
|
|
464
|
+
**No Deduplication:**
|
|
465
|
+
|
|
466
|
+
* Generate new clipPath for each use
|
|
467
|
+
* **Rejected:** Wasteful, larger files
|
|
468
|
+
|
|
469
|
+
**Post-Processing Deduplication:**
|
|
470
|
+
|
|
471
|
+
* Generate all, then deduplicate
|
|
472
|
+
* **Rejected:** More complex, same result
|
|
473
|
+
|
|
474
|
+
=== Trade-offs
|
|
475
|
+
|
|
476
|
+
**Accepted:**
|
|
477
|
+
|
|
478
|
+
* Memory for cache (small)
|
|
479
|
+
* Hash lookup overhead (negligible)
|
|
480
|
+
|
|
481
|
+
**Gained:**
|
|
482
|
+
|
|
483
|
+
* Significantly smaller output
|
|
484
|
+
* Better performance
|
|
485
|
+
* Cleaner SVG
|
|
486
|
+
|
|
487
|
+
== Decision 8: Strict vs Lenient Mode
|
|
488
|
+
|
|
489
|
+
=== Context
|
|
490
|
+
|
|
491
|
+
PostScript files may use unsupported operators. Need graceful degradation option.
|
|
492
|
+
|
|
493
|
+
=== Decision
|
|
494
|
+
|
|
495
|
+
Support both modes:
|
|
496
|
+
|
|
497
|
+
**Strict Mode:**
|
|
498
|
+
|
|
499
|
+
[source,ruby]
|
|
500
|
+
----
|
|
501
|
+
if command
|
|
502
|
+
command.call(@context)
|
|
503
|
+
else
|
|
504
|
+
raise UnsupportedOperatorError, "Unknown operator: #{op}"
|
|
505
|
+
end
|
|
506
|
+
----
|
|
507
|
+
|
|
508
|
+
**Lenient Mode (Default):**
|
|
509
|
+
|
|
510
|
+
[source,ruby]
|
|
511
|
+
----
|
|
512
|
+
if command
|
|
513
|
+
command.call(@context)
|
|
514
|
+
else
|
|
515
|
+
@context.svg_output[:paths] << "<!-- Unhandled: #{op} -->"
|
|
516
|
+
end
|
|
517
|
+
----
|
|
518
|
+
|
|
519
|
+
=== Rationale
|
|
520
|
+
|
|
521
|
+
**Benefits:**
|
|
522
|
+
|
|
523
|
+
* **Flexibility:** Users choose behavior
|
|
524
|
+
* **Compatibility:** Lenient mode maximizes conversion success
|
|
525
|
+
* **Debugging:** Strict mode helps development
|
|
526
|
+
* **Transparency:** HTML comments show what was skipped
|
|
527
|
+
|
|
528
|
+
=== Alternatives Considered
|
|
529
|
+
|
|
530
|
+
**Strict Only:**
|
|
531
|
+
|
|
532
|
+
* Always fail on unknown operators
|
|
533
|
+
* **Rejected:** Too rigid, limits usefulness
|
|
534
|
+
|
|
535
|
+
**Lenient Only:**
|
|
536
|
+
|
|
537
|
+
* Always continue, never fail
|
|
538
|
+
* **Rejected:** Hides errors, harder to debug
|
|
539
|
+
|
|
540
|
+
**Warning Mode:**
|
|
541
|
+
|
|
542
|
+
* Continue but log warnings
|
|
543
|
+
* **Deferred:** Could add as third option
|
|
544
|
+
|
|
545
|
+
=== Trade-offs
|
|
546
|
+
|
|
547
|
+
**Accepted:**
|
|
548
|
+
|
|
549
|
+
* Default lenient mode may hide errors
|
|
550
|
+
* Users must understand mode implications
|
|
551
|
+
|
|
552
|
+
**Gained:**
|
|
553
|
+
|
|
554
|
+
* Maximum compatibility
|
|
555
|
+
* Development-friendly strict mode
|
|
556
|
+
* User control
|
|
557
|
+
|
|
558
|
+
== Decision 9: Token-Based vs AST-Based Parsing
|
|
559
|
+
|
|
560
|
+
=== Context
|
|
561
|
+
|
|
562
|
+
Parser could generate abstract syntax tree or simple token stream.
|
|
563
|
+
|
|
564
|
+
=== Decision
|
|
565
|
+
|
|
566
|
+
Generate flat token stream:
|
|
567
|
+
|
|
568
|
+
[source,ruby]
|
|
569
|
+
----
|
|
570
|
+
Token = Struct.new(:type, :value)
|
|
571
|
+
|
|
572
|
+
[
|
|
573
|
+
Token(:number, "42"),
|
|
574
|
+
Token(:operator, "moveto"),
|
|
575
|
+
# ...
|
|
576
|
+
]
|
|
577
|
+
----
|
|
578
|
+
|
|
579
|
+
=== Rationale
|
|
580
|
+
|
|
581
|
+
**Benefits:**
|
|
582
|
+
|
|
583
|
+
* **Simplicity:** Easy to implement and understand
|
|
584
|
+
* **Memory:** Lower overhead than tree
|
|
585
|
+
* **Sufficient:** PostScript is procedural, not hierarchical
|
|
586
|
+
* **Performance:** Single pass, no tree construction
|
|
587
|
+
|
|
588
|
+
**PostScript Nature:**
|
|
589
|
+
|
|
590
|
+
* Stack-based execution model
|
|
591
|
+
* Procedures are token lists (not AST nodes)
|
|
592
|
+
* No need for parse tree
|
|
593
|
+
|
|
594
|
+
=== Alternatives Considered
|
|
595
|
+
|
|
596
|
+
**Full AST:**
|
|
597
|
+
|
|
598
|
+
[source,ruby]
|
|
599
|
+
----
|
|
600
|
+
Program(
|
|
601
|
+
statements: [
|
|
602
|
+
NumberLiteral(42),
|
|
603
|
+
Command(name: "moveto", args: [...])
|
|
604
|
+
]
|
|
605
|
+
)
|
|
606
|
+
----
|
|
607
|
+
|
|
608
|
+
**Rejected:** Unnecessary complexity, memory overhead
|
|
609
|
+
|
|
610
|
+
**Hybrid Approach:**
|
|
611
|
+
|
|
612
|
+
* Flat tokens + parse trees for procedures
|
|
613
|
+
* **Rejected:** Inconsistent, complex
|
|
614
|
+
|
|
615
|
+
=== Trade-offs
|
|
616
|
+
|
|
617
|
+
**Accepted:**
|
|
618
|
+
|
|
619
|
+
* Procedures are just token lists
|
|
620
|
+
* No high-level program structure
|
|
621
|
+
|
|
622
|
+
**Gained:**
|
|
623
|
+
|
|
624
|
+
* Simple, fast implementation
|
|
625
|
+
* Low memory usage
|
|
626
|
+
* Matches PostScript model
|
|
627
|
+
|
|
628
|
+
== Decision 10: Ruby Standard Library Only (Minimal Dependencies)
|
|
629
|
+
|
|
630
|
+
=== Context
|
|
631
|
+
|
|
632
|
+
Could use various gems for parsing, SVG generation, etc.
|
|
633
|
+
|
|
634
|
+
=== Decision
|
|
635
|
+
|
|
636
|
+
Minimize dependencies:
|
|
637
|
+
|
|
638
|
+
* No parser generator (PEG, Racc, etc.)
|
|
639
|
+
* No XML library (Nokogiri, REXML, etc.) for generation
|
|
640
|
+
* Use standard library where possible
|
|
641
|
+
|
|
642
|
+
**Current Dependencies:**
|
|
643
|
+
|
|
644
|
+
* `moxml` - XML generation (optional, legacy)
|
|
645
|
+
* Standard library only for core functionality
|
|
646
|
+
|
|
647
|
+
=== Rationale
|
|
648
|
+
|
|
649
|
+
**Benefits:**
|
|
650
|
+
|
|
651
|
+
* **Reliability:** Fewer dependency updates
|
|
652
|
+
* **Security:** Smaller attack surface
|
|
653
|
+
* **Portability:** Easier to install
|
|
654
|
+
* **Simplicity:** Less to learn
|
|
655
|
+
|
|
656
|
+
**Measurements:**
|
|
657
|
+
|
|
658
|
+
* Tokenizer: ~160 lines, no dependencies
|
|
659
|
+
* Sufficient for PostScript syntax
|
|
660
|
+
|
|
661
|
+
=== Alternatives Considered
|
|
662
|
+
|
|
663
|
+
**Parser Generator:**
|
|
664
|
+
|
|
665
|
+
* Use Parslet, Racc, or similar
|
|
666
|
+
* **Rejected:** Overkill for simple PostScript syntax
|
|
667
|
+
|
|
668
|
+
**XML Library:**
|
|
669
|
+
|
|
670
|
+
* Use Nokogiri for SVG generation
|
|
671
|
+
* **Rejected:** String concatenation is sufficient
|
|
672
|
+
|
|
673
|
+
**More Gems:**
|
|
674
|
+
|
|
675
|
+
* Math libraries, optimization libraries
|
|
676
|
+
* **Rejected:** Standard library is adequate
|
|
677
|
+
|
|
678
|
+
=== Trade-offs
|
|
679
|
+
|
|
680
|
+
**Accepted:**
|
|
681
|
+
|
|
682
|
+
* Manual parsing code
|
|
683
|
+
* String-based SVG generation
|
|
684
|
+
|
|
685
|
+
**Gained:**
|
|
686
|
+
|
|
687
|
+
* Minimal dependencies
|
|
688
|
+
* Easier maintenance
|
|
689
|
+
* Faster installation
|
|
690
|
+
|
|
691
|
+
== Decision 11: Bottom-Up SVG Coordinate Transform
|
|
692
|
+
|
|
693
|
+
=== Context
|
|
694
|
+
|
|
695
|
+
PostScript uses bottom-left origin, SVG uses top-left. Need coordinate conversion.
|
|
696
|
+
|
|
697
|
+
=== Decision
|
|
698
|
+
|
|
699
|
+
Apply global Y-flip transformation:
|
|
700
|
+
|
|
701
|
+
[source,xml]
|
|
702
|
+
----
|
|
703
|
+
<g transform="translate(0 HEIGHT) scale(1 -1)">
|
|
704
|
+
<!-- All content here -->
|
|
705
|
+
</g>
|
|
706
|
+
----
|
|
707
|
+
|
|
708
|
+
=== Rationale
|
|
709
|
+
|
|
710
|
+
**Benefits:**
|
|
711
|
+
|
|
712
|
+
* **Simplicity:** Single transform handles all content
|
|
713
|
+
* **Correctness:** Preserves PostScript coordinates exactly
|
|
714
|
+
* **Browser Support:** All SVG renderers support this
|
|
715
|
+
* **Performance:** No per-coordinate transformation
|
|
716
|
+
|
|
717
|
+
**Alternative Approaches:**
|
|
718
|
+
|
|
719
|
+
* Transform each coordinate individually
|
|
720
|
+
* **Rejected:** Error-prone, slower
|
|
721
|
+
|
|
722
|
+
* Use `transform` attribute on each element
|
|
723
|
+
* **Rejected:** More verbose, inconsistent
|
|
724
|
+
|
|
725
|
+
=== Trade-offs
|
|
726
|
+
|
|
727
|
+
**Accepted:**
|
|
728
|
+
|
|
729
|
+
* Extra `<g>` wrapper element
|
|
730
|
+
* Nested coordinate system
|
|
731
|
+
|
|
732
|
+
**Gained:**
|
|
733
|
+
|
|
734
|
+
* Simple, correct implementation
|
|
735
|
+
* Matches PostScript exactly
|
|
736
|
+
* Single point of transformation
|
|
737
|
+
|
|
738
|
+
== Decision 12: Inline Path Coordinates vs Transform Groups
|
|
739
|
+
|
|
740
|
+
=== Context
|
|
741
|
+
|
|
742
|
+
Transformed paths could store original coordinates with transforms or transform coordinates.
|
|
743
|
+
|
|
744
|
+
=== Decision
|
|
745
|
+
|
|
746
|
+
Hybrid approach:
|
|
747
|
+
|
|
748
|
+
* **Simple transforms** (translation, uniform scale): Bake into coordinates
|
|
749
|
+
* **Complex transforms** (rotation, non-uniform scale): Use `<g transform>`
|
|
750
|
+
|
|
751
|
+
[source,ruby]
|
|
752
|
+
----
|
|
753
|
+
def needs_transform_wrapper?
|
|
754
|
+
has_rotation = ctm.b.abs > 1e-10 || ctm.c.abs > 1e-10
|
|
755
|
+
has_nonuniform_scale = (ctm.a.abs - ctm.d.abs).abs > 1e-6
|
|
756
|
+
has_rotation || has_nonuniform_scale
|
|
757
|
+
end
|
|
758
|
+
----
|
|
759
|
+
|
|
760
|
+
=== Rationale
|
|
761
|
+
|
|
762
|
+
**Benefits:**
|
|
763
|
+
|
|
764
|
+
* **Optimization:** Simpler SVG for simple transforms
|
|
765
|
+
* **Correctness:** Preserve complex transforms exactly
|
|
766
|
+
* **Readability:** Cleaner output for common cases
|
|
767
|
+
|
|
768
|
+
**Examples:**
|
|
769
|
+
|
|
770
|
+
[source,xml]
|
|
771
|
+
----
|
|
772
|
+
<!-- Simple: Coordinates transformed -->
|
|
773
|
+
<path d="M 110 120 L 130 140" />
|
|
774
|
+
|
|
775
|
+
<!-- Complex: Transform wrapper -->
|
|
776
|
+
<g transform="rotate(45)">
|
|
777
|
+
<path d="M 10 20 L 30 40" />
|
|
778
|
+
</g>
|
|
779
|
+
----
|
|
780
|
+
|
|
781
|
+
=== Trade-offs
|
|
782
|
+
|
|
783
|
+
**Accepted:**
|
|
784
|
+
|
|
785
|
+
* More complex generation logic
|
|
786
|
+
* Conditional transformation
|
|
787
|
+
|
|
788
|
+
**Gained:**
|
|
789
|
+
|
|
790
|
+
* Optimized common case
|
|
791
|
+
* Correct complex case
|
|
792
|
+
* Readable output
|
|
793
|
+
|
|
794
|
+
== Design Principles Summary
|
|
795
|
+
|
|
796
|
+
=== SOLID Principles
|
|
797
|
+
|
|
798
|
+
**Single Responsibility:**
|
|
799
|
+
|
|
800
|
+
* Each class has one reason to change
|
|
801
|
+
* Tokenizer: lexical analysis only
|
|
802
|
+
* Interpreter: execution only
|
|
803
|
+
* Commands: single operator each
|
|
804
|
+
|
|
805
|
+
**Open/Closed:**
|
|
806
|
+
|
|
807
|
+
* System open for extension (new operators)
|
|
808
|
+
* Closed for modification (core unchanged)
|
|
809
|
+
* Command pattern enables this
|
|
810
|
+
|
|
811
|
+
**Liskov Substitution:**
|
|
812
|
+
|
|
813
|
+
* All commands implement `call(context)`
|
|
814
|
+
* Can substitute any command implementation
|
|
815
|
+
* Polymorphic dispatch works correctly
|
|
816
|
+
|
|
817
|
+
**Interface Segregation:**
|
|
818
|
+
|
|
819
|
+
* Context provides focused interfaces
|
|
820
|
+
* Commands only access what they need
|
|
821
|
+
* No fat interfaces
|
|
822
|
+
|
|
823
|
+
**Dependency Inversion:**
|
|
824
|
+
|
|
825
|
+
* Depend on abstractions (Base command)
|
|
826
|
+
* Not on concrete implementations
|
|
827
|
+
* Registry mediates dependencies
|
|
828
|
+
|
|
829
|
+
=== Additional Principles
|
|
830
|
+
|
|
831
|
+
**YAGNI (You Aren't Gonna Need It):**
|
|
832
|
+
|
|
833
|
+
* No speculative features
|
|
834
|
+
* Implement only what's needed
|
|
835
|
+
* Example: Streaming deferred
|
|
836
|
+
|
|
837
|
+
**KISS (Keep It Simple, Stupid):**
|
|
838
|
+
|
|
839
|
+
* Prefer simple solutions
|
|
840
|
+
* Example: Token stream vs AST
|
|
841
|
+
* Example: String concatenation vs XML library
|
|
842
|
+
|
|
843
|
+
**DRY (Don't Repeat Yourself):**
|
|
844
|
+
|
|
845
|
+
* Command pattern eliminates repetition
|
|
846
|
+
* Number formatting centralized
|
|
847
|
+
* State cloning abstracted
|
|
848
|
+
|
|
849
|
+
== Performance Characteristics
|
|
850
|
+
|
|
851
|
+
=== Time Complexity
|
|
852
|
+
|
|
853
|
+
**Overall:** O(n + t + e)
|
|
854
|
+
|
|
855
|
+
* n = input length
|
|
856
|
+
* t = number of tokens
|
|
857
|
+
* e = number of SVG elements
|
|
858
|
+
|
|
859
|
+
**Breakdown:**
|
|
860
|
+
|
|
861
|
+
* Tokenization: O(n) single pass
|
|
862
|
+
* Interpretation: O(t) single pass
|
|
863
|
+
* Generation: O(e) during interpretation
|
|
864
|
+
|
|
865
|
+
=== Space Complexity
|
|
866
|
+
|
|
867
|
+
**Overall:** O(t + e + d)
|
|
868
|
+
|
|
869
|
+
* t = token array size
|
|
870
|
+
* e = SVG output size
|
|
871
|
+
* d = stack/state depth
|
|
872
|
+
|
|
873
|
+
**Breakdown:**
|
|
874
|
+
|
|
875
|
+
* Tokens: O(t) stored in memory
|
|
876
|
+
* SVG buffer: O(e) accumulated output
|
|
877
|
+
* State stack: O(d) gsave nesting
|
|
878
|
+
|
|
879
|
+
=== Optimization Opportunities
|
|
880
|
+
|
|
881
|
+
**Potential Improvements:**
|
|
882
|
+
|
|
883
|
+
1. **Streaming Tokenization:** Process tokens as produced
|
|
884
|
+
2. **Instance Pooling:** Reuse command instances
|
|
885
|
+
3. **Path Optimization:** Merge consecutive moves
|
|
886
|
+
4. **Lazy String Building:** Use rope data structure
|
|
887
|
+
|
|
888
|
+
**Current Decision:** Prioritize simplicity over optimization until performance issues demonstrated.
|
|
889
|
+
|
|
890
|
+
== Future Considerations
|
|
891
|
+
|
|
892
|
+
=== Extensibility Points
|
|
893
|
+
|
|
894
|
+
**New Operators:**
|
|
895
|
+
|
|
896
|
+
* Add command class
|
|
897
|
+
* Register in registry
|
|
898
|
+
* Write tests
|
|
899
|
+
|
|
900
|
+
**New Output Formats:**
|
|
901
|
+
|
|
902
|
+
* Create alternative generator
|
|
903
|
+
* Implement same interface
|
|
904
|
+
* Plug into interpreter
|
|
905
|
+
|
|
906
|
+
**Performance Enhancements:**
|
|
907
|
+
|
|
908
|
+
* Instance pooling (backward compatible)
|
|
909
|
+
* Streaming (internal change)
|
|
910
|
+
* Parallel execution (major change)
|
|
911
|
+
|
|
912
|
+
=== Known Limitations
|
|
913
|
+
|
|
914
|
+
**Single-Threaded:**
|
|
915
|
+
|
|
916
|
+
* Current implementation not thread-safe
|
|
917
|
+
* Could be parallelized with immutable state
|
|
918
|
+
|
|
919
|
+
**Memory Usage:**
|
|
920
|
+
|
|
921
|
+
* All tokens in memory
|
|
922
|
+
* All SVG output buffered
|
|
923
|
+
* Could stream for large files
|
|
924
|
+
|
|
925
|
+
**Precision:**
|
|
926
|
+
|
|
927
|
+
* 3 decimal places for coordinates
|
|
928
|
+
* Sufficient for most graphics
|
|
929
|
+
* Could be configurable
|
|
930
|
+
|
|
931
|
+
== Lessons Learned
|
|
932
|
+
|
|
933
|
+
=== What Worked Well
|
|
934
|
+
|
|
935
|
+
**Command Pattern:**
|
|
936
|
+
|
|
937
|
+
* Proved highly extensible
|
|
938
|
+
* Easy to test
|
|
939
|
+
* Clean organization
|
|
940
|
+
|
|
941
|
+
**Three-Stage Pipeline:**
|
|
942
|
+
|
|
943
|
+
* Clear separation
|
|
944
|
+
* Independent testing
|
|
945
|
+
* Easy debugging
|
|
946
|
+
|
|
947
|
+
**Immutable State:**
|
|
948
|
+
|
|
949
|
+
* Prevented many bugs
|
|
950
|
+
* Made debugging easier
|
|
951
|
+
* Worth the overhead
|
|
952
|
+
|
|
953
|
+
=== What Could Improve
|
|
954
|
+
|
|
955
|
+
**Generator Coupling:**
|
|
956
|
+
|
|
957
|
+
* SVG generation tightly coupled to interpreter
|
|
958
|
+
* Could be more modular
|
|
959
|
+
* Acceptable for current use case
|
|
960
|
+
|
|
961
|
+
**Error Messages:**
|
|
962
|
+
|
|
963
|
+
* Could be more helpful
|
|
964
|
+
* Need better context
|
|
965
|
+
* Improvement opportunity
|
|
966
|
+
|
|
967
|
+
**Documentation:**
|
|
968
|
+
|
|
969
|
+
* More inline comments needed
|
|
970
|
+
* Better examples wanted
|
|
971
|
+
* This document helps
|
|
972
|
+
|
|
973
|
+
== Conclusion
|
|
974
|
+
|
|
975
|
+
Postsvg's architecture prioritizes:
|
|
976
|
+
|
|
977
|
+
1. **Correctness:** Get the right answer
|
|
978
|
+
2. **Simplicity:** Easy to understand and maintain
|
|
979
|
+
3. **Extensibility:** Easy to add features
|
|
980
|
+
4. **Performance:** Good enough for typical use
|
|
981
|
+
|
|
982
|
+
These priorities drive all architectural decisions. When trade-offs are necessary, correctness and simplicity take precedence over performance and features.
|
|
983
|
+
|
|
984
|
+
== Next Steps
|
|
985
|
+
|
|
986
|
+
* Review individual architecture documents for details
|
|
987
|
+
* Explore link:../development.adoc[Development Guide] for contributing
|
|
988
|
+
* See link:../api-reference.adoc[API Reference] for implementation details
|
|
989
|
+
* Check link:../troubleshooting.adoc[Troubleshooting] for common issues
|
|
990
|
+
|
|
991
|
+
== Bibliography
|
|
992
|
+
|
|
993
|
+
* link:conversion-pipeline.adoc[Conversion Pipeline Documentation]
|
|
994
|
+
* link:command-registry.adoc[Command Registry Architecture]
|
|
995
|
+
* link:graphics-state-model.adoc[Graphics State Model]
|
|
996
|
+
* Design Patterns: Elements of Reusable Object-Oriented Software (Gang of Four)
|
|
997
|
+
* Clean Code: A Handbook of Agile Software Craftsmanship (Robert C. Martin)
|
|
998
|
+
* The Pragmatic Programmer (Hunt & Thomas)
|
|
999
|
+
* Architectural Decision Records (ADR) Methodology
|