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,903 @@
|
|
|
1
|
+
= Conversion Pipeline
|
|
2
|
+
:page-nav_order: 1
|
|
3
|
+
:page-parent: Core Concepts
|
|
4
|
+
|
|
5
|
+
== Purpose
|
|
6
|
+
|
|
7
|
+
This document provides a detailed explanation of Postsvg's three-stage conversion pipeline that transforms PostScript into SVG. Understanding this pipeline is essential for comprehending how Postsvg processes PostScript commands and generates standards-compliant SVG output.
|
|
8
|
+
|
|
9
|
+
== References
|
|
10
|
+
|
|
11
|
+
* link:../index.adoc[Documentation Home]
|
|
12
|
+
* link:../concepts.adoc[Core Concepts Overview]
|
|
13
|
+
* link:../architecture.adoc[Architecture Documentation]
|
|
14
|
+
* link:../architecture/conversion-pipeline.adoc[Conversion Pipeline Implementation]
|
|
15
|
+
* link:postscript-language.adoc[PostScript Language Fundamentals]
|
|
16
|
+
|
|
17
|
+
== Concepts
|
|
18
|
+
|
|
19
|
+
**Three-Stage Architecture**:: The conversion process is divided into tokenization, interpretation, and generation stages for clear separation of concerns.
|
|
20
|
+
|
|
21
|
+
**Tokenization**:: The process of breaking PostScript source code into discrete tokens (numbers, operators, strings, etc.).
|
|
22
|
+
|
|
23
|
+
**Interpretation**:: Executing PostScript tokens using a stack-based model to build graphics operations.
|
|
24
|
+
|
|
25
|
+
**SVG Generation**:: Transforming interpreted graphics operations into standards-compliant SVG markup.
|
|
26
|
+
|
|
27
|
+
**Pipeline Flow**:: Data flows unidirectionally from PostScript source through tokens to execution context to SVG output.
|
|
28
|
+
|
|
29
|
+
== Pipeline Overview
|
|
30
|
+
|
|
31
|
+
The conversion pipeline implements a clean three-stage architecture that separates concerns and enables independent testing and optimization of each stage.
|
|
32
|
+
|
|
33
|
+
.Three-Stage Conversion Pipeline
|
|
34
|
+
[source]
|
|
35
|
+
----
|
|
36
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
37
|
+
│ STAGE 1: TOKENIZATION │
|
|
38
|
+
│ │
|
|
39
|
+
│ PostScript Source (.ps/.eps) │
|
|
40
|
+
│ │ │
|
|
41
|
+
│ ▼ │
|
|
42
|
+
│ ┌─────────────┐ │
|
|
43
|
+
│ │ Tokenizer │ Lexical Analysis │
|
|
44
|
+
│ └──────┬──────┘ │
|
|
45
|
+
│ │ │
|
|
46
|
+
│ ▼ │
|
|
47
|
+
│ Token Stream: [ │
|
|
48
|
+
│ { type: "number", value: 100 }, │
|
|
49
|
+
│ { type: "number", value: 200 }, │
|
|
50
|
+
│ { type: "operator", value: "moveto" }, │
|
|
51
|
+
│ ... │
|
|
52
|
+
│ ] │
|
|
53
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
54
|
+
│
|
|
55
|
+
▼
|
|
56
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
57
|
+
│ STAGE 2: INTERPRETATION │
|
|
58
|
+
│ │
|
|
59
|
+
│ Token Stream │
|
|
60
|
+
│ │ │
|
|
61
|
+
│ ▼ │
|
|
62
|
+
│ ┌─────────────────┐ │
|
|
63
|
+
│ │ Interpreter │ Stack-Based Execution │
|
|
64
|
+
│ └────────┬────────┘ │
|
|
65
|
+
│ │ │
|
|
66
|
+
│ ├──> ┌──────────────────┐ │
|
|
67
|
+
│ │ │ ExecutionContext │ │
|
|
68
|
+
│ │ │ - Operand Stack │ │
|
|
69
|
+
│ │ │ - Dict Stack │ │
|
|
70
|
+
│ │ │ - Graphics State│ │
|
|
71
|
+
│ │ └──────────────────┘ │
|
|
72
|
+
│ │ │
|
|
73
|
+
│ ▼ │
|
|
74
|
+
│ Graphics Operations: [ │
|
|
75
|
+
│ { type: :moveto, x: 100, y: 200 }, │
|
|
76
|
+
│ { type: :lineto, x: 300, y: 200 }, │
|
|
77
|
+
│ { type: :stroke, color: "#000000" }, │
|
|
78
|
+
│ ... │
|
|
79
|
+
│ ] │
|
|
80
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
81
|
+
│
|
|
82
|
+
▼
|
|
83
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
84
|
+
│ STAGE 3: SVG GENERATION │
|
|
85
|
+
│ │
|
|
86
|
+
│ Graphics Operations │
|
|
87
|
+
│ │ │
|
|
88
|
+
│ ▼ │
|
|
89
|
+
│ ┌──────────────┐ │
|
|
90
|
+
│ │ SVG Generator│ XML Document Construction │
|
|
91
|
+
│ └──────┬───────┘ │
|
|
92
|
+
│ │ │
|
|
93
|
+
│ ▼ │
|
|
94
|
+
│ SVG Document: │
|
|
95
|
+
│ <?xml version="1.0"?> │
|
|
96
|
+
│ <svg viewBox="0 0 400 400"> │
|
|
97
|
+
│ <path d="M 100 200 L 300 200" stroke="#000" /> │
|
|
98
|
+
│ </svg> │
|
|
99
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
100
|
+
----
|
|
101
|
+
|
|
102
|
+
== Stage 1: Tokenization
|
|
103
|
+
|
|
104
|
+
=== Purpose of Tokenization
|
|
105
|
+
|
|
106
|
+
Tokenization (also called lexical analysis) breaks the PostScript source into meaningful units called tokens. This stage handles:
|
|
107
|
+
|
|
108
|
+
* Character-by-character parsing of PostScript syntax
|
|
109
|
+
* Recognition of PostScript language elements
|
|
110
|
+
* Creation of typed token objects
|
|
111
|
+
* String and comment processing
|
|
112
|
+
|
|
113
|
+
=== Token Types
|
|
114
|
+
|
|
115
|
+
The [`Tokenizer`](../../lib/postsvg/tokenizer.rb:8) recognizes these token types:
|
|
116
|
+
|
|
117
|
+
**Numeric Tokens**::
|
|
118
|
+
Numbers in PostScript can be integers or floating-point values.
|
|
119
|
+
+
|
|
120
|
+
[source,postscript]
|
|
121
|
+
----
|
|
122
|
+
42 % Integer → { type: "number", value: 42 }
|
|
123
|
+
3.14159 % Float → { type: "number", value: 3.14159 }
|
|
124
|
+
-10 % Negative → { type: "number", value: -10 }
|
|
125
|
+
1.5e-3 % Scientific → { type: "number", value: 0.0015 }
|
|
126
|
+
----
|
|
127
|
+
|
|
128
|
+
**Operator Tokens**::
|
|
129
|
+
PostScript commands that perform operations.
|
|
130
|
+
+
|
|
131
|
+
[source,postscript]
|
|
132
|
+
----
|
|
133
|
+
moveto % → { type: "operator", value: "moveto" }
|
|
134
|
+
lineto % → { type: "operator", value: "lineto" }
|
|
135
|
+
stroke % → { type: "operator", value: "stroke" }
|
|
136
|
+
----
|
|
137
|
+
|
|
138
|
+
**Name Tokens**::
|
|
139
|
+
Identifiers starting with `/` used for dictionary keys and variable names.
|
|
140
|
+
+
|
|
141
|
+
[source,postscript]
|
|
142
|
+
----
|
|
143
|
+
/MyFont % → { type: "name", value: "MyFont" }
|
|
144
|
+
/x % → { type: "name", value: "x" }
|
|
145
|
+
----
|
|
146
|
+
|
|
147
|
+
**String Tokens**::
|
|
148
|
+
Text enclosed in parentheses or angle brackets.
|
|
149
|
+
+
|
|
150
|
+
[source,postscript]
|
|
151
|
+
----
|
|
152
|
+
(Hello) % → { type: "string", value: "Hello" }
|
|
153
|
+
<48656c6c6f> % Hex → { type: "hex_string", value: "Hello" }
|
|
154
|
+
----
|
|
155
|
+
|
|
156
|
+
**Delimiter Tokens**::
|
|
157
|
+
Brackets and braces for arrays and procedures.
|
|
158
|
+
+
|
|
159
|
+
[source,postscript]
|
|
160
|
+
----
|
|
161
|
+
[ % → { type: "brace", value: "[" }
|
|
162
|
+
] % → { type: "brace", value: "]" }
|
|
163
|
+
{ % → { type: "brace", value: "{" }
|
|
164
|
+
} % → { type: "brace", value: "}" }
|
|
165
|
+
----
|
|
166
|
+
|
|
167
|
+
=== Tokenization Process
|
|
168
|
+
|
|
169
|
+
.Tokenization Example
|
|
170
|
+
[example]
|
|
171
|
+
====
|
|
172
|
+
Given this PostScript code:
|
|
173
|
+
|
|
174
|
+
[source,postscript]
|
|
175
|
+
----
|
|
176
|
+
10 20 moveto
|
|
177
|
+
30 40 lineto
|
|
178
|
+
stroke
|
|
179
|
+
----
|
|
180
|
+
|
|
181
|
+
The tokenizer produces this token stream:
|
|
182
|
+
|
|
183
|
+
[source,ruby]
|
|
184
|
+
----
|
|
185
|
+
[
|
|
186
|
+
{ type: "number", value: 10 },
|
|
187
|
+
{ type: "number", value: 20 },
|
|
188
|
+
{ type: "operator", value: "moveto" },
|
|
189
|
+
{ type: "number", value: 30 },
|
|
190
|
+
{ type: "number", value: 40 },
|
|
191
|
+
{ type: "operator", value: "lineto" },
|
|
192
|
+
{ type: "operator", value: "stroke" }
|
|
193
|
+
]
|
|
194
|
+
----
|
|
195
|
+
====
|
|
196
|
+
|
|
197
|
+
=== Handling Complex Structures
|
|
198
|
+
|
|
199
|
+
**Procedures (Executable Arrays)**::
|
|
200
|
+
Code blocks enclosed in braces are tokenized as procedure boundaries.
|
|
201
|
+
+
|
|
202
|
+
[source,postscript]
|
|
203
|
+
----
|
|
204
|
+
{ 100 100 moveto 200 200 lineto stroke }
|
|
205
|
+
----
|
|
206
|
+
+
|
|
207
|
+
Becomes:
|
|
208
|
+
+
|
|
209
|
+
[source,ruby]
|
|
210
|
+
----
|
|
211
|
+
[
|
|
212
|
+
{ type: "brace", value: "{" },
|
|
213
|
+
{ type: "number", value: 100 },
|
|
214
|
+
{ type: "number", value: 100 },
|
|
215
|
+
{ type: "operator", value: "moveto" },
|
|
216
|
+
{ type: "number", value: 200 },
|
|
217
|
+
{ type: "number", value: 200 },
|
|
218
|
+
{ type: "operator", value: "lineto" },
|
|
219
|
+
{ type: "operator", value: "stroke" },
|
|
220
|
+
{ type: "brace", value: "}" }
|
|
221
|
+
]
|
|
222
|
+
----
|
|
223
|
+
|
|
224
|
+
**Arrays**::
|
|
225
|
+
Data arrays enclosed in square brackets.
|
|
226
|
+
+
|
|
227
|
+
[source,postscript]
|
|
228
|
+
----
|
|
229
|
+
[1 2 3 4]
|
|
230
|
+
----
|
|
231
|
+
+
|
|
232
|
+
Becomes:
|
|
233
|
+
+
|
|
234
|
+
[source,ruby]
|
|
235
|
+
----
|
|
236
|
+
[
|
|
237
|
+
{ type: "brace", value: "[" },
|
|
238
|
+
{ type: "number", value: 1 },
|
|
239
|
+
{ type: "number", value: 2 },
|
|
240
|
+
{ type: "number", value: 3 },
|
|
241
|
+
{ type: "number", value: 4 },
|
|
242
|
+
{ type: "brace", value: "]" }
|
|
243
|
+
]
|
|
244
|
+
----
|
|
245
|
+
|
|
246
|
+
**Comments**::
|
|
247
|
+
Comments starting with `%` are ignored during tokenization.
|
|
248
|
+
+
|
|
249
|
+
[source,postscript]
|
|
250
|
+
----
|
|
251
|
+
100 200 moveto % Move to starting point
|
|
252
|
+
----
|
|
253
|
+
+
|
|
254
|
+
The comment is stripped; only the command tokens remain.
|
|
255
|
+
|
|
256
|
+
=== Tokenizer Implementation
|
|
257
|
+
|
|
258
|
+
The tokenizer is implemented in [`lib/postsvg/tokenizer.rb`](../../lib/postsvg/tokenizer.rb:1).
|
|
259
|
+
|
|
260
|
+
.Using the Tokenizer
|
|
261
|
+
[example]
|
|
262
|
+
====
|
|
263
|
+
[source,ruby]
|
|
264
|
+
----
|
|
265
|
+
require 'postsvg'
|
|
266
|
+
|
|
267
|
+
ps_code = <<~PS
|
|
268
|
+
newpath
|
|
269
|
+
50 50 moveto
|
|
270
|
+
150 50 lineto
|
|
271
|
+
100 150 lineto
|
|
272
|
+
closepath
|
|
273
|
+
fill
|
|
274
|
+
PS
|
|
275
|
+
|
|
276
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
277
|
+
|
|
278
|
+
tokens.each do |token|
|
|
279
|
+
puts "#{token[:type].ljust(10)} → #{token[:value]}"
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Output:
|
|
283
|
+
# operator → newpath
|
|
284
|
+
# number → 50
|
|
285
|
+
# number → 50
|
|
286
|
+
# operator → moveto
|
|
287
|
+
# number → 150
|
|
288
|
+
# number → 50
|
|
289
|
+
# operator → lineto
|
|
290
|
+
# number → 100
|
|
291
|
+
# number → 150
|
|
292
|
+
# operator → lineto
|
|
293
|
+
# operator → closepath
|
|
294
|
+
# operator → fill
|
|
295
|
+
----
|
|
296
|
+
====
|
|
297
|
+
|
|
298
|
+
== Stage 2: Interpretation
|
|
299
|
+
|
|
300
|
+
=== Purpose of Interpretation
|
|
301
|
+
|
|
302
|
+
The interpretation stage executes PostScript tokens using a stack-based model. This stage:
|
|
303
|
+
|
|
304
|
+
* Maintains operand and dictionary stacks
|
|
305
|
+
* Executes operators through the command pattern
|
|
306
|
+
* Builds graphics state progressively
|
|
307
|
+
* Handles control flow (loops, conditionals)
|
|
308
|
+
* Manages graphics state stack (gsave/grestore)
|
|
309
|
+
|
|
310
|
+
=== Execution Model
|
|
311
|
+
|
|
312
|
+
PostScript uses a **stack-based execution model** where:
|
|
313
|
+
|
|
314
|
+
1. Operands are pushed onto the operand stack
|
|
315
|
+
2. Operators pop operands, perform operations, and push results
|
|
316
|
+
3. The graphics state accumulates drawing commands
|
|
317
|
+
|
|
318
|
+
.Stack-Based Execution
|
|
319
|
+
[example]
|
|
320
|
+
====
|
|
321
|
+
[source,postscript]
|
|
322
|
+
----
|
|
323
|
+
10 20 add % Push 10, push 20, add (pop 2, push 30)
|
|
324
|
+
3 mul % Push 3, multiply (pop 30 and 3, push 90)
|
|
325
|
+
----
|
|
326
|
+
|
|
327
|
+
Execution trace:
|
|
328
|
+
|
|
329
|
+
[source]
|
|
330
|
+
----
|
|
331
|
+
Step 1: Push 10
|
|
332
|
+
Stack: [10]
|
|
333
|
+
|
|
334
|
+
Step 2: Push 20
|
|
335
|
+
Stack: [10, 20]
|
|
336
|
+
|
|
337
|
+
Step 3: Execute 'add'
|
|
338
|
+
Pop: 20, 10
|
|
339
|
+
Calculate: 10 + 20 = 30
|
|
340
|
+
Push: 30
|
|
341
|
+
Stack: [30]
|
|
342
|
+
|
|
343
|
+
Step 4: Push 3
|
|
344
|
+
Stack: [30, 3]
|
|
345
|
+
|
|
346
|
+
Step 5: Execute 'mul'
|
|
347
|
+
Pop: 3, 30
|
|
348
|
+
Calculate: 30 * 3 = 90
|
|
349
|
+
Push: 90
|
|
350
|
+
Stack: [90]
|
|
351
|
+
----
|
|
352
|
+
====
|
|
353
|
+
|
|
354
|
+
=== Execution Context
|
|
355
|
+
|
|
356
|
+
The [`ExecutionContext`](../../lib/postsvg/execution_context.rb:9) maintains the runtime state:
|
|
357
|
+
|
|
358
|
+
**Operand Stack**:: Values waiting for operators
|
|
359
|
+
**Dictionary Stack**:: Variable definitions and scopes
|
|
360
|
+
**Graphics State**:: Current drawing parameters
|
|
361
|
+
**Graphics State Stack**:: Saved states from `gsave`
|
|
362
|
+
**SVG Output Buffer**:: Accumulating SVG elements
|
|
363
|
+
|
|
364
|
+
.Execution Context Structure
|
|
365
|
+
[source]
|
|
366
|
+
----
|
|
367
|
+
ExecutionContext
|
|
368
|
+
├── Operand Stack
|
|
369
|
+
│ ├── 100
|
|
370
|
+
│ ├── 200
|
|
371
|
+
│ └── "Hello"
|
|
372
|
+
│
|
|
373
|
+
├── Dictionary Stack
|
|
374
|
+
│ ├── System Dictionary (built-in operators)
|
|
375
|
+
│ └── User Dictionary (user definitions)
|
|
376
|
+
│
|
|
377
|
+
├── Graphics State
|
|
378
|
+
│ ├── Current Path: [(M 10,10), (L 90,10)]
|
|
379
|
+
│ ├── Current Point: (90, 10)
|
|
380
|
+
│ ├── Line Width: 1.0
|
|
381
|
+
│ ├── Stroke Color: #000000
|
|
382
|
+
│ ├── Fill Color: #808080
|
|
383
|
+
│ └── Transformation Matrix: [1 0 0 1 0 0]
|
|
384
|
+
│
|
|
385
|
+
├── Graphics State Stack (for gsave/grestore)
|
|
386
|
+
│ ├── [Saved State 1]
|
|
387
|
+
│ └── [Saved State 2]
|
|
388
|
+
│
|
|
389
|
+
└── SVG Output Buffer
|
|
390
|
+
├── <path d="M 10 10 L 90 10" stroke="#000"/>
|
|
391
|
+
└── <path d="M 50 50 L 150 150" fill="#808080"/>
|
|
392
|
+
----
|
|
393
|
+
|
|
394
|
+
=== Command Pattern
|
|
395
|
+
|
|
396
|
+
Each PostScript operator is implemented as a command object following the Command Pattern.
|
|
397
|
+
|
|
398
|
+
.Command Pattern Example
|
|
399
|
+
[example]
|
|
400
|
+
====
|
|
401
|
+
The `moveto` operator is implemented in [`lib/postsvg/commands/path/moveto.rb`](../../lib/postsvg/commands/path/moveto.rb:1):
|
|
402
|
+
|
|
403
|
+
[source,ruby]
|
|
404
|
+
----
|
|
405
|
+
module Postsvg
|
|
406
|
+
module Commands
|
|
407
|
+
module Path
|
|
408
|
+
class Moveto < Base
|
|
409
|
+
def execute(context)
|
|
410
|
+
y = context.pop_number # Pop Y coordinate
|
|
411
|
+
x = context.pop_number # Pop X coordinate
|
|
412
|
+
context.graphics_state.moveto(x, y)
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
----
|
|
419
|
+
|
|
420
|
+
When the interpreter encounters a `moveto` token:
|
|
421
|
+
|
|
422
|
+
1. Lookup command object in registry
|
|
423
|
+
2. Call `command.execute(context)`
|
|
424
|
+
3. Command pops operands from stack
|
|
425
|
+
4. Command updates graphics state
|
|
426
|
+
====
|
|
427
|
+
|
|
428
|
+
=== Interpreter Implementation
|
|
429
|
+
|
|
430
|
+
The interpreter is implemented in [`lib/postsvg/interpreter.rb`](../../lib/postsvg/interpreter.rb:1).
|
|
431
|
+
|
|
432
|
+
.Interpreting PostScript
|
|
433
|
+
[example]
|
|
434
|
+
====
|
|
435
|
+
[source,ruby]
|
|
436
|
+
----
|
|
437
|
+
require 'postsvg'
|
|
438
|
+
|
|
439
|
+
# PostScript code
|
|
440
|
+
ps_code = <<~PS
|
|
441
|
+
newpath
|
|
442
|
+
100 100 moveto
|
|
443
|
+
200 100 lineto
|
|
444
|
+
200 200 lineto
|
|
445
|
+
100 200 lineto
|
|
446
|
+
closepath
|
|
447
|
+
0.5 setgray
|
|
448
|
+
fill
|
|
449
|
+
PS
|
|
450
|
+
|
|
451
|
+
# Tokenize
|
|
452
|
+
tokens = Postsvg::Tokenizer.tokenize(ps_code)
|
|
453
|
+
|
|
454
|
+
# Interpret
|
|
455
|
+
interpreter = Postsvg::Interpreter.new
|
|
456
|
+
bbox = { llx: 0, lly: 0, urx: 300, ury: 300, width: 300, height: 300 }
|
|
457
|
+
result = interpreter.interpret(tokens, bbox)
|
|
458
|
+
|
|
459
|
+
# Result contains:
|
|
460
|
+
# - :svg - Complete SVG document
|
|
461
|
+
# - :paths - Individual path elements
|
|
462
|
+
# - :elements - All SVG elements
|
|
463
|
+
puts result[:paths].length # 1 path (the filled square)
|
|
464
|
+
----
|
|
465
|
+
====
|
|
466
|
+
|
|
467
|
+
=== Graphics State Management
|
|
468
|
+
|
|
469
|
+
The graphics state tracks all drawing parameters:
|
|
470
|
+
|
|
471
|
+
**Path State**::
|
|
472
|
+
* Current path segments
|
|
473
|
+
* Current point (x, y)
|
|
474
|
+
* Path closed or open
|
|
475
|
+
|
|
476
|
+
**Color State**::
|
|
477
|
+
* Fill color (RGB, grayscale, CMYK)
|
|
478
|
+
* Stroke color
|
|
479
|
+
* Color space
|
|
480
|
+
|
|
481
|
+
**Line Attributes**::
|
|
482
|
+
* Line width
|
|
483
|
+
* Line cap style (butt, round, square)
|
|
484
|
+
* Line join style (miter, round, bevel)
|
|
485
|
+
* Dash pattern and offset
|
|
486
|
+
* Miter limit
|
|
487
|
+
|
|
488
|
+
**Transformation Matrix**::
|
|
489
|
+
* Current transformation matrix (CTM)
|
|
490
|
+
* Enables translation, rotation, scaling
|
|
491
|
+
|
|
492
|
+
.Graphics State Stack Operations
|
|
493
|
+
[example]
|
|
494
|
+
====
|
|
495
|
+
[source,postscript]
|
|
496
|
+
----
|
|
497
|
+
gsave % Save current state
|
|
498
|
+
2 setlinewidth
|
|
499
|
+
1 0 0 setrgbcolor
|
|
500
|
+
newpath
|
|
501
|
+
50 50 moveto
|
|
502
|
+
150 50 lineto
|
|
503
|
+
stroke
|
|
504
|
+
grestore % Restore previous state
|
|
505
|
+
|
|
506
|
+
% Line width and color are back to original values
|
|
507
|
+
----
|
|
508
|
+
|
|
509
|
+
The graphics state stack allows nesting:
|
|
510
|
+
|
|
511
|
+
[source]
|
|
512
|
+
----
|
|
513
|
+
Initial State: linewidth=1, color=black
|
|
514
|
+
│
|
|
515
|
+
├─ gsave → Save State 1
|
|
516
|
+
│ Modified: linewidth=2, color=red
|
|
517
|
+
│ │
|
|
518
|
+
│ ├─ gsave → Save State 2
|
|
519
|
+
│ │ Modified: linewidth=5, color=blue
|
|
520
|
+
│ │ (draw with blue, width 5)
|
|
521
|
+
│ │
|
|
522
|
+
│ └─ grestore → Restore to State 1
|
|
523
|
+
│ Back to: linewidth=2, color=red
|
|
524
|
+
│ (draw with red, width 2)
|
|
525
|
+
│
|
|
526
|
+
└─ grestore → Restore to Initial State
|
|
527
|
+
Back to: linewidth=1, color=black
|
|
528
|
+
----
|
|
529
|
+
====
|
|
530
|
+
|
|
531
|
+
== Stage 3: SVG Generation
|
|
532
|
+
|
|
533
|
+
=== Purpose of SVG Generation
|
|
534
|
+
|
|
535
|
+
The SVG generation stage transforms the accumulated graphics operations into standards-compliant SVG markup. This stage handles:
|
|
536
|
+
|
|
537
|
+
* Coordinate system transformation (PostScript → SVG)
|
|
538
|
+
* Path data formatting
|
|
539
|
+
* Style attribute generation
|
|
540
|
+
* SVG document structure
|
|
541
|
+
* Optimization and cleanup
|
|
542
|
+
|
|
543
|
+
=== SVG Document Structure
|
|
544
|
+
|
|
545
|
+
The generator creates well-formed SVG documents:
|
|
546
|
+
|
|
547
|
+
[source,xml]
|
|
548
|
+
----
|
|
549
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
550
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
551
|
+
width="400"
|
|
552
|
+
height="400"
|
|
553
|
+
viewBox="0 0 400 400">
|
|
554
|
+
<defs>
|
|
555
|
+
<!-- Patterns, gradients, clip paths -->
|
|
556
|
+
</defs>
|
|
557
|
+
<g transform="translate(0 400) scale(1 -1)">
|
|
558
|
+
<!-- Graphics elements with Y-axis flip -->
|
|
559
|
+
<path d="M 100 100 L 300 100" stroke="#000000" stroke-width="1"/>
|
|
560
|
+
</g>
|
|
561
|
+
</svg>
|
|
562
|
+
----
|
|
563
|
+
|
|
564
|
+
**Key Elements:**
|
|
565
|
+
|
|
566
|
+
`xmlns` attribute:: SVG namespace declaration
|
|
567
|
+
`viewBox`:: Coordinate system bounds from BoundingBox
|
|
568
|
+
`width`/`height`:: Physical dimensions
|
|
569
|
+
`<defs>`:: Reusable definitions (patterns, gradients)
|
|
570
|
+
`<g>` transform:: Y-axis flip for coordinate system conversion
|
|
571
|
+
|
|
572
|
+
=== Coordinate System Transformation
|
|
573
|
+
|
|
574
|
+
PostScript and SVG use different coordinate systems:
|
|
575
|
+
|
|
576
|
+
**PostScript**: Origin at bottom-left, Y increases upward
|
|
577
|
+
**SVG**: Origin at top-left, Y increases downward
|
|
578
|
+
|
|
579
|
+
.Coordinate System Difference
|
|
580
|
+
[source]
|
|
581
|
+
----
|
|
582
|
+
PostScript (0,0 at bottom-left) SVG (0,0 at top-left)
|
|
583
|
+
|
|
584
|
+
100 ┌────────┐ 400 ┌────────┐ 0
|
|
585
|
+
│ │ 0 │ │
|
|
586
|
+
│ ● │ │ ● │
|
|
587
|
+
0 └────────┘ └────────┘ 400
|
|
588
|
+
0 400 0 400
|
|
589
|
+
|
|
590
|
+
Point (200, 50) in PostScript
|
|
591
|
+
becomes (200, 350) in SVG
|
|
592
|
+
----
|
|
593
|
+
|
|
594
|
+
The generator applies a transformation:
|
|
595
|
+
|
|
596
|
+
[source,xml]
|
|
597
|
+
----
|
|
598
|
+
<g transform="translate(0 400) scale(1 -1)">
|
|
599
|
+
<!-- Graphics here use PostScript coordinates -->
|
|
600
|
+
</g>
|
|
601
|
+
----
|
|
602
|
+
|
|
603
|
+
This transform:
|
|
604
|
+
1. Translates origin to bottom-left
|
|
605
|
+
2. Flips Y-axis (scale Y by -1)
|
|
606
|
+
|
|
607
|
+
=== Path Generation
|
|
608
|
+
|
|
609
|
+
PostScript paths are converted to SVG path data:
|
|
610
|
+
|
|
611
|
+
.Path Conversion
|
|
612
|
+
[example]
|
|
613
|
+
====
|
|
614
|
+
PostScript path operations:
|
|
615
|
+
|
|
616
|
+
[source,postscript]
|
|
617
|
+
----
|
|
618
|
+
newpath
|
|
619
|
+
100 100 moveto
|
|
620
|
+
200 100 lineto
|
|
621
|
+
200 200 lineto
|
|
622
|
+
100 200 lineto
|
|
623
|
+
closepath
|
|
624
|
+
----
|
|
625
|
+
|
|
626
|
+
Becomes SVG path data:
|
|
627
|
+
|
|
628
|
+
[source,xml]
|
|
629
|
+
----
|
|
630
|
+
<path d="M 100 100 L 200 100 L 200 200 L 100 200 Z"/>
|
|
631
|
+
----
|
|
632
|
+
|
|
633
|
+
Path command mapping:
|
|
634
|
+
|
|
635
|
+
* `moveto` → `M x y`
|
|
636
|
+
* `lineto` → `L x y`
|
|
637
|
+
* `curveto` → `C x1 y1 x2 y2 x3 y3`
|
|
638
|
+
* `closepath` → `Z`
|
|
639
|
+
====
|
|
640
|
+
|
|
641
|
+
=== Style Generation
|
|
642
|
+
|
|
643
|
+
Graphics state attributes become SVG style attributes:
|
|
644
|
+
|
|
645
|
+
**Stroke Operations**::
|
|
646
|
+
[source,xml]
|
|
647
|
+
----
|
|
648
|
+
<path d="..."
|
|
649
|
+
stroke="#000000"
|
|
650
|
+
stroke-width="2"
|
|
651
|
+
fill="none"/>
|
|
652
|
+
----
|
|
653
|
+
|
|
654
|
+
**Fill Operations**::
|
|
655
|
+
[source,xml]
|
|
656
|
+
----
|
|
657
|
+
<path d="..."
|
|
658
|
+
fill="#808080"
|
|
659
|
+
stroke="none"/>
|
|
660
|
+
----
|
|
661
|
+
|
|
662
|
+
**Line Attributes**::
|
|
663
|
+
[source,xml]
|
|
664
|
+
----
|
|
665
|
+
<path d="..."
|
|
666
|
+
stroke-linecap="round"
|
|
667
|
+
stroke-linejoin="miter"
|
|
668
|
+
stroke-dasharray="5,3"/>
|
|
669
|
+
----
|
|
670
|
+
|
|
671
|
+
=== SVG Generator Implementation
|
|
672
|
+
|
|
673
|
+
The generator is implemented in [`lib/postsvg/svg_generator.rb`](../../lib/postsvg/svg_generator.rb:1).
|
|
674
|
+
|
|
675
|
+
.Using the SVG Generator
|
|
676
|
+
[example]
|
|
677
|
+
====
|
|
678
|
+
[source,ruby]
|
|
679
|
+
----
|
|
680
|
+
require 'postsvg'
|
|
681
|
+
|
|
682
|
+
# The generator is used internally by the interpreter
|
|
683
|
+
generator = Postsvg::SvgGenerator.new
|
|
684
|
+
|
|
685
|
+
# Add paths during interpretation
|
|
686
|
+
generator.add_path(
|
|
687
|
+
[{ type: :moveto, x: 10, y: 10 },
|
|
688
|
+
{ type: :lineto, x: 90, y: 90 }],
|
|
689
|
+
graphics_state,
|
|
690
|
+
:stroke
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
# Generate final SVG
|
|
694
|
+
svg = generator.generate(
|
|
695
|
+
width: 100,
|
|
696
|
+
height: 100,
|
|
697
|
+
viewbox: "0 0 100 100"
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
puts svg
|
|
701
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
|
702
|
+
# <svg xmlns="http://www.w3.org/2000/svg" ...>
|
|
703
|
+
# <path d="M 10 10 L 90 90" stroke="#000" .../>
|
|
704
|
+
# </svg>
|
|
705
|
+
----
|
|
706
|
+
====
|
|
707
|
+
|
|
708
|
+
== Pipeline Data Flow
|
|
709
|
+
|
|
710
|
+
=== Complete Flow Example
|
|
711
|
+
|
|
712
|
+
.End-to-End Conversion
|
|
713
|
+
[example]
|
|
714
|
+
====
|
|
715
|
+
Starting with PostScript:
|
|
716
|
+
|
|
717
|
+
[source,postscript]
|
|
718
|
+
----
|
|
719
|
+
%!PS-Adobe-3.0
|
|
720
|
+
%%BoundingBox: 0 0 200 200
|
|
721
|
+
newpath
|
|
722
|
+
50 50 moveto
|
|
723
|
+
150 150 lineto
|
|
724
|
+
1 0 0 setrgbcolor
|
|
725
|
+
2 setlinewidth
|
|
726
|
+
stroke
|
|
727
|
+
showpage
|
|
728
|
+
----
|
|
729
|
+
|
|
730
|
+
**Stage 1: Tokenization**
|
|
731
|
+
|
|
732
|
+
[source,ruby]
|
|
733
|
+
----
|
|
734
|
+
tokens = [
|
|
735
|
+
{ type: "operator", value: "newpath" },
|
|
736
|
+
{ type: "number", value: 50 },
|
|
737
|
+
{ type: "number", value: 50 },
|
|
738
|
+
{ type: "operator", value: "moveto" },
|
|
739
|
+
{ type: "number", value: 150 },
|
|
740
|
+
{ type: "number", value: 150 },
|
|
741
|
+
{ type: "operator", value: "lineto" },
|
|
742
|
+
{ type: "number", value: 1 },
|
|
743
|
+
{ type: "number", value: 0 },
|
|
744
|
+
{ type: "number", value: 0 },
|
|
745
|
+
{ type: "operator", value: "setrgbcolor" },
|
|
746
|
+
{ type: "number", value: 2 },
|
|
747
|
+
{ type: "operator", value: "setlinewidth" },
|
|
748
|
+
{ type: "operator", value: "stroke" },
|
|
749
|
+
{ type: "operator", value: "showpage" }
|
|
750
|
+
]
|
|
751
|
+
----
|
|
752
|
+
|
|
753
|
+
**Stage 2: Interpretation**
|
|
754
|
+
|
|
755
|
+
[source,ruby]
|
|
756
|
+
----
|
|
757
|
+
# Execute tokens sequentially:
|
|
758
|
+
# - newpath: Clear current path
|
|
759
|
+
# - 50, 50, moveto: Set current point to (50, 50)
|
|
760
|
+
# - 150, 150, lineto: Add line to (150, 150)
|
|
761
|
+
# - 1, 0, 0, setrgbcolor: Set stroke color to red
|
|
762
|
+
# - 2, setlinewidth: Set line width to 2
|
|
763
|
+
# - stroke: Paint the path with current stroke settings
|
|
764
|
+
|
|
765
|
+
graphics_operations = [
|
|
766
|
+
{ type: :path, segments: [
|
|
767
|
+
{ type: :moveto, x: 50, y: 50 },
|
|
768
|
+
{ type: :lineto, x: 150, y: 150 }
|
|
769
|
+
],
|
|
770
|
+
operation: :stroke,
|
|
771
|
+
stroke_color: "#FF0000",
|
|
772
|
+
stroke_width: 2
|
|
773
|
+
}
|
|
774
|
+
]
|
|
775
|
+
----
|
|
776
|
+
|
|
777
|
+
**Stage 3: SVG Generation**
|
|
778
|
+
|
|
779
|
+
[source,xml]
|
|
780
|
+
----
|
|
781
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
782
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
783
|
+
width="200"
|
|
784
|
+
height="200"
|
|
785
|
+
viewBox="0 0 200 200">
|
|
786
|
+
<g transform="translate(0 200) scale(1 -1)">
|
|
787
|
+
<path d="M 50 50 L 150 150"
|
|
788
|
+
stroke="#FF0000"
|
|
789
|
+
stroke-width="2"
|
|
790
|
+
fill="none"/>
|
|
791
|
+
</g>
|
|
792
|
+
</svg>
|
|
793
|
+
----
|
|
794
|
+
====
|
|
795
|
+
|
|
796
|
+
== Error Handling
|
|
797
|
+
|
|
798
|
+
=== Strict vs. Lenient Modes
|
|
799
|
+
|
|
800
|
+
The pipeline supports two error handling modes:
|
|
801
|
+
|
|
802
|
+
**Strict Mode**::
|
|
803
|
+
Fails immediately on unknown operators or execution errors.
|
|
804
|
+
+
|
|
805
|
+
[source,ruby]
|
|
806
|
+
----
|
|
807
|
+
interpreter = Postsvg::Interpreter.new(strict_mode: true)
|
|
808
|
+
begin
|
|
809
|
+
result = interpreter.interpret(tokens, bbox)
|
|
810
|
+
rescue Postsvg::UnsupportedOperatorError => e
|
|
811
|
+
puts "Unsupported: #{e.message}"
|
|
812
|
+
end
|
|
813
|
+
----
|
|
814
|
+
|
|
815
|
+
**Lenient Mode**::
|
|
816
|
+
Continues processing, adding SVG comments for errors.
|
|
817
|
+
+
|
|
818
|
+
[source,ruby]
|
|
819
|
+
----
|
|
820
|
+
interpreter = Postsvg::Interpreter.new(strict_mode: false)
|
|
821
|
+
result = interpreter.interpret(tokens, bbox)
|
|
822
|
+
# SVG may contain: <!-- Unhandled operator: customop -->
|
|
823
|
+
----
|
|
824
|
+
|
|
825
|
+
=== Error Recovery
|
|
826
|
+
|
|
827
|
+
In lenient mode, the pipeline recovers from:
|
|
828
|
+
|
|
829
|
+
* Unknown operators
|
|
830
|
+
* Stack underflow
|
|
831
|
+
* Type mismatches
|
|
832
|
+
* Invalid coordinates
|
|
833
|
+
|
|
834
|
+
.Error Recovery Example
|
|
835
|
+
[example]
|
|
836
|
+
====
|
|
837
|
+
[source,postscript]
|
|
838
|
+
----
|
|
839
|
+
10 moveto % ERROR: moveto needs 2 args
|
|
840
|
+
100 200 moveto % This succeeds
|
|
841
|
+
----
|
|
842
|
+
|
|
843
|
+
Lenient mode output:
|
|
844
|
+
|
|
845
|
+
[source,xml]
|
|
846
|
+
----
|
|
847
|
+
<!-- Error executing moveto: insufficient operands -->
|
|
848
|
+
<path d="M 100 200 ..." />
|
|
849
|
+
----
|
|
850
|
+
====
|
|
851
|
+
|
|
852
|
+
== Performance Characteristics
|
|
853
|
+
|
|
854
|
+
=== Pipeline Performance
|
|
855
|
+
|
|
856
|
+
**Tokenization**: O(n) where n = character count
|
|
857
|
+
|
|
858
|
+
* Single-pass character scanning
|
|
859
|
+
* Minimal backtracking
|
|
860
|
+
* Efficient string handling
|
|
861
|
+
|
|
862
|
+
**Interpretation**: O(t × c) where:
|
|
863
|
+
|
|
864
|
+
* t = token count
|
|
865
|
+
* c = average command complexity
|
|
866
|
+
|
|
867
|
+
**SVG Generation**: O(p) where p = path complexity
|
|
868
|
+
|
|
869
|
+
* Linear path traversal
|
|
870
|
+
* Minimal string operations
|
|
871
|
+
|
|
872
|
+
=== Optimization Strategies
|
|
873
|
+
|
|
874
|
+
**Token Stream Optimization**::
|
|
875
|
+
* Tokens created once, reused
|
|
876
|
+
* No token copying
|
|
877
|
+
|
|
878
|
+
**Execution Optimization**::
|
|
879
|
+
* Hash-based command lookup: O(1)
|
|
880
|
+
* Stack operations: O(1)
|
|
881
|
+
* State snapshots for gsave/grestore
|
|
882
|
+
|
|
883
|
+
**SVG Optimization**::
|
|
884
|
+
* Path data minimization
|
|
885
|
+
* Redundant attribute removal
|
|
886
|
+
* ClipPath deduplication
|
|
887
|
+
|
|
888
|
+
== Next Steps
|
|
889
|
+
|
|
890
|
+
* Explore link:graphics-state.adoc[Graphics State Management] for state tracking details
|
|
891
|
+
* Review link:coordinate-systems.adoc[Coordinate Systems] for transformation details
|
|
892
|
+
* See link:path-operations.adoc[Path Operations] for path construction
|
|
893
|
+
* Check link:svg-generation.adoc[SVG Generation] for output details
|
|
894
|
+
* Read link:../architecture/conversion-pipeline.adoc[Pipeline Implementation] for code details
|
|
895
|
+
|
|
896
|
+
== Bibliography
|
|
897
|
+
|
|
898
|
+
* link:../architecture/conversion-pipeline.adoc[Conversion Pipeline Implementation]
|
|
899
|
+
* link:postscript-language.adoc[PostScript Language Fundamentals]
|
|
900
|
+
* link:graphics-state.adoc[Graphics State Management]
|
|
901
|
+
* link:../api-reference/interpreter.adoc[Interpreter API Reference]
|
|
902
|
+
* link:https://www.adobe.com/jp/print/postscript/pdfs/PLRM.pdf[PostScript Language Reference Manual]
|
|
903
|
+
* link:https://www.w3.org/TR/SVG/[SVG Specification]
|