postsvg 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +105 -0
  3. data/CLAUDE.md +173 -0
  4. data/Gemfile +2 -3
  5. data/README.adoc +456 -179
  6. data/Rakefile +100 -0
  7. data/TODO.roadmap/00-architecture.md +139 -0
  8. data/TODO.roadmap/01-autoload-migration.md +39 -0
  9. data/TODO.roadmap/02-isolate-dormant-code.md +51 -0
  10. data/TODO.roadmap/03-domain-model.md +66 -0
  11. data/TODO.roadmap/04-lexer.md +40 -0
  12. data/TODO.roadmap/05-parser.md +43 -0
  13. data/TODO.roadmap/06-graphics-state.md +45 -0
  14. data/TODO.roadmap/07-matrix-and-color.md +37 -0
  15. data/TODO.roadmap/08-svg-builder.md +51 -0
  16. data/TODO.roadmap/09-renderer.md +52 -0
  17. data/TODO.roadmap/10-visitor.md +58 -0
  18. data/TODO.roadmap/11-operator-coverage.md +103 -0
  19. data/TODO.roadmap/12-svg-domain.md +62 -0
  20. data/TODO.roadmap/13-translation-handlers.md +69 -0
  21. data/TODO.roadmap/14-ps-serializer.md +49 -0
  22. data/TODO.roadmap/15-cli-and-public-api.md +47 -0
  23. data/TODO.roadmap/16-specs.md +84 -0
  24. data/TODO.roadmap/17-docs-sync.md +47 -0
  25. data/TODO.roadmap/18-performance-and-determinism.md +47 -0
  26. data/TODO.roadmap/19-error-model.md +45 -0
  27. data/TODO.roadmap/20-font-and-text.md +46 -0
  28. data/TODO.roadmap/21-images.md +45 -0
  29. data/TODO.roadmap/22-forms-and-resources.md +25 -0
  30. data/TODO.roadmap/23-level2-level3.md +52 -0
  31. data/TODO.roadmap/24-ci-and-release.md +42 -0
  32. data/TODO.roadmap/README.md +77 -0
  33. data/docs/.gitignore +29 -0
  34. data/docs/CHANGELOG.md +114 -0
  35. data/docs/COMPLETE_DOCUMENTATION_STATUS.md +376 -0
  36. data/docs/DEPLOYMENT.md +456 -0
  37. data/docs/DEPLOYMENT_INSTRUCTIONS.md +229 -0
  38. data/docs/DOCUMENTATION_PLAN.md +425 -0
  39. data/docs/FINAL_SUMMARY.md +657 -0
  40. data/docs/Gemfile +15 -0
  41. data/docs/README.md +327 -0
  42. data/docs/_config.yml +99 -0
  43. data/docs/advanced-topics.adoc +370 -0
  44. data/docs/api-reference/colors.adoc +705 -0
  45. data/docs/api-reference/converter.adoc +699 -0
  46. data/docs/api-reference/execution-context.adoc +1210 -0
  47. data/docs/api-reference/graphics-state.adoc +1070 -0
  48. data/docs/api-reference/interpreter.adoc +810 -0
  49. data/docs/api-reference/matrix.adoc +1179 -0
  50. data/docs/api-reference/path-builder.adoc +1284 -0
  51. data/docs/api-reference/postsvg-module.adoc +388 -0
  52. data/docs/api-reference/svg-generator.adoc +891 -0
  53. data/docs/api-reference/tokenizer.adoc +925 -0
  54. data/docs/api-reference.adoc +221 -0
  55. data/docs/architecture/command-registry.adoc +1191 -0
  56. data/docs/architecture/conversion-pipeline.adoc +746 -0
  57. data/docs/architecture/design-decisions.adoc +999 -0
  58. data/docs/architecture/generator-stage.adoc +1115 -0
  59. data/docs/architecture/graphics-state-model.adoc +1089 -0
  60. data/docs/architecture/interpreter-stage.adoc +1125 -0
  61. data/docs/architecture/parser-stage.adoc +1051 -0
  62. data/docs/architecture.adoc +354 -0
  63. data/docs/cli-reference/batch-command.adoc +616 -0
  64. data/docs/cli-reference/check-command.adoc +677 -0
  65. data/docs/cli-reference/cli-options.adoc +802 -0
  66. data/docs/cli-reference/convert-command.adoc +462 -0
  67. data/docs/cli-reference/version-command.adoc +296 -0
  68. data/docs/cli-reference.adoc +317 -0
  69. data/docs/concepts/conversion-pipeline.adoc +903 -0
  70. data/docs/concepts/coordinate-systems.adoc +836 -0
  71. data/docs/concepts/graphics-state.adoc +861 -0
  72. data/docs/concepts/path-operations.adoc +1076 -0
  73. data/docs/concepts/postscript-language.adoc +859 -0
  74. data/docs/concepts/svg-generation.adoc +937 -0
  75. data/docs/concepts.adoc +198 -0
  76. data/docs/contributing.adoc +443 -0
  77. data/docs/development.adoc +420 -0
  78. data/docs/faq.adoc +493 -0
  79. data/docs/getting-started/basic-usage.adoc +538 -0
  80. data/docs/getting-started/common-workflows.adoc +577 -0
  81. data/docs/getting-started/first-conversion.adoc +492 -0
  82. data/docs/getting-started/installation.adoc +534 -0
  83. data/docs/getting-started.adoc +94 -0
  84. data/docs/index.adoc +248 -0
  85. data/docs/optimization.adoc +196 -0
  86. data/docs/ps2svg_compatibility.adoc +149 -0
  87. data/docs/quick-reference.adoc +453 -0
  88. data/docs/sitemap.adoc +337 -0
  89. data/docs/troubleshooting.adoc +486 -0
  90. data/docs/validation.adoc +772 -0
  91. data/exe/postsvg +1 -0
  92. data/lib/postsvg/cli.rb +104 -57
  93. data/lib/postsvg/color.rb +132 -0
  94. data/lib/postsvg/errors.rb +68 -3
  95. data/lib/postsvg/format_number.rb +22 -0
  96. data/lib/postsvg/graphics_context.rb +80 -0
  97. data/lib/postsvg/graphics_stack.rb +43 -0
  98. data/lib/postsvg/model/literals/array.rb +41 -0
  99. data/lib/postsvg/model/literals/dictionary.rb +34 -0
  100. data/lib/postsvg/model/literals/hex.rb +37 -0
  101. data/lib/postsvg/model/literals/name.rb +40 -0
  102. data/lib/postsvg/model/literals/number.rb +36 -0
  103. data/lib/postsvg/model/literals/procedure.rb +41 -0
  104. data/lib/postsvg/model/literals/string.rb +30 -0
  105. data/lib/postsvg/model/literals.rb +19 -0
  106. data/lib/postsvg/model/operator.rb +58 -0
  107. data/lib/postsvg/model/operators/arithmetic.rb +264 -0
  108. data/lib/postsvg/model/operators/boolean.rb +182 -0
  109. data/lib/postsvg/model/operators/color.rb +74 -0
  110. data/lib/postsvg/model/operators/container.rb +186 -0
  111. data/lib/postsvg/model/operators/control_flow.rb +119 -0
  112. data/lib/postsvg/model/operators/device.rb +21 -0
  113. data/lib/postsvg/model/operators/dictionary.rb +118 -0
  114. data/lib/postsvg/model/operators/font.rb +121 -0
  115. data/lib/postsvg/model/operators/graphics_state.rb +84 -0
  116. data/lib/postsvg/model/operators/painting.rb +29 -0
  117. data/lib/postsvg/model/operators/path.rb +169 -0
  118. data/lib/postsvg/model/operators/stack.rb +72 -0
  119. data/lib/postsvg/model/operators/transformations.rb +103 -0
  120. data/lib/postsvg/model/operators.rb +89 -0
  121. data/lib/postsvg/model/program.rb +68 -0
  122. data/lib/postsvg/model/token.rb +43 -0
  123. data/lib/postsvg/model.rb +17 -0
  124. data/lib/postsvg/options.rb +29 -0
  125. data/lib/postsvg/renderer.rb +85 -0
  126. data/lib/postsvg/serializer.rb +325 -0
  127. data/lib/postsvg/source/ast_builder.rb +308 -0
  128. data/lib/postsvg/source/lexer.rb +322 -0
  129. data/lib/postsvg/source/operand_stack.rb +55 -0
  130. data/lib/postsvg/source.rb +21 -0
  131. data/lib/postsvg/svg/attribute_parser.rb +45 -0
  132. data/lib/postsvg/svg/clip_path_registry.rb +44 -0
  133. data/lib/postsvg/svg/document.rb +22 -0
  134. data/lib/postsvg/svg/element.rb +84 -0
  135. data/lib/postsvg/svg/elements/circle.rb +36 -0
  136. data/lib/postsvg/svg/elements/clip_path.rb +26 -0
  137. data/lib/postsvg/svg/elements/defs.rb +24 -0
  138. data/lib/postsvg/svg/elements/ellipse.rb +38 -0
  139. data/lib/postsvg/svg/elements/group.rb +37 -0
  140. data/lib/postsvg/svg/elements/image.rb +35 -0
  141. data/lib/postsvg/svg/elements/line.rb +36 -0
  142. data/lib/postsvg/svg/elements/path.rb +32 -0
  143. data/lib/postsvg/svg/elements/polygon.rb +12 -0
  144. data/lib/postsvg/svg/elements/polyline.rb +32 -0
  145. data/lib/postsvg/svg/elements/rect.rb +44 -0
  146. data/lib/postsvg/svg/elements/svg.rb +39 -0
  147. data/lib/postsvg/svg/elements/text.rb +42 -0
  148. data/lib/postsvg/svg/elements.rb +31 -0
  149. data/lib/postsvg/svg/paint.rb +34 -0
  150. data/lib/postsvg/svg/parser.rb +39 -0
  151. data/lib/postsvg/svg/path_data/command.rb +27 -0
  152. data/lib/postsvg/svg/path_data/parser.rb +82 -0
  153. data/lib/postsvg/svg/path_data.rb +17 -0
  154. data/lib/postsvg/svg/stroke.rb +31 -0
  155. data/lib/postsvg/svg/transform_list.rb +59 -0
  156. data/lib/postsvg/svg.rb +22 -0
  157. data/lib/postsvg/svg_builder.rb +249 -0
  158. data/lib/postsvg/translation/arc_converter.rb +86 -0
  159. data/lib/postsvg/translation/bounding_box.rb +59 -0
  160. data/lib/postsvg/translation/context.rb +34 -0
  161. data/lib/postsvg/translation/handler_registry.rb +38 -0
  162. data/lib/postsvg/translation/handlers/circle_handler.rb +28 -0
  163. data/lib/postsvg/translation/handlers/clip_path_handler.rb +13 -0
  164. data/lib/postsvg/translation/handlers/defs_handler.rb +15 -0
  165. data/lib/postsvg/translation/handlers/ellipse_handler.rb +31 -0
  166. data/lib/postsvg/translation/handlers/group_handler.rb +21 -0
  167. data/lib/postsvg/translation/handlers/image_handler.rb +20 -0
  168. data/lib/postsvg/translation/handlers/line_handler.rb +23 -0
  169. data/lib/postsvg/translation/handlers/open_handler.rb +18 -0
  170. data/lib/postsvg/translation/handlers/path_handler.rb +356 -0
  171. data/lib/postsvg/translation/handlers/polygon_handler.rb +33 -0
  172. data/lib/postsvg/translation/handlers/polyline_handler.rb +31 -0
  173. data/lib/postsvg/translation/handlers/rect_handler.rb +27 -0
  174. data/lib/postsvg/translation/handlers/shared.rb +110 -0
  175. data/lib/postsvg/translation/handlers/svg_handler.rb +25 -0
  176. data/lib/postsvg/translation/handlers/text_handler.rb +56 -0
  177. data/lib/postsvg/translation/handlers.rb +25 -0
  178. data/lib/postsvg/translation/ps_renderer.rb +105 -0
  179. data/lib/postsvg/translation/record_emitter.rb +35 -0
  180. data/lib/postsvg/translation.rb +17 -0
  181. data/lib/postsvg/version.rb +1 -1
  182. data/lib/postsvg/visitors/ps_visitor/arithmetic.rb +125 -0
  183. data/lib/postsvg/visitors/ps_visitor/boolean.rb +105 -0
  184. data/lib/postsvg/visitors/ps_visitor/color.rb +53 -0
  185. data/lib/postsvg/visitors/ps_visitor/common.rb +66 -0
  186. data/lib/postsvg/visitors/ps_visitor/container.rb +164 -0
  187. data/lib/postsvg/visitors/ps_visitor/control_flow.rb +110 -0
  188. data/lib/postsvg/visitors/ps_visitor/device.rb +20 -0
  189. data/lib/postsvg/visitors/ps_visitor/dictionary.rb +89 -0
  190. data/lib/postsvg/visitors/ps_visitor/font.rb +93 -0
  191. data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +55 -0
  192. data/lib/postsvg/visitors/ps_visitor/painting.rb +90 -0
  193. data/lib/postsvg/visitors/ps_visitor/path.rb +112 -0
  194. data/lib/postsvg/visitors/ps_visitor/stack.rb +47 -0
  195. data/lib/postsvg/visitors/ps_visitor/transformations.rb +101 -0
  196. data/lib/postsvg/visitors/ps_visitor.rb +208 -0
  197. data/lib/postsvg/visitors.rb +9 -0
  198. data/lib/postsvg.rb +93 -59
  199. data/lychee.toml +86 -0
  200. metadata +216 -11
  201. data/postsvg.gemspec +0 -38
@@ -0,0 +1,937 @@
1
+ = SVG Generation
2
+ :page-nav_order: 5
3
+ :page-parent: Core Concepts
4
+
5
+ == Purpose
6
+
7
+ This document explains how Postsvg generates SVG output from interpreted PostScript graphics operations. Understanding the SVG generation process helps when debugging output, optimizing performance, and extending Postsvg's capabilities.
8
+
9
+ == References
10
+
11
+ * link:../index.adoc[Documentation Home]
12
+ * link:../concepts.adoc[Core Concepts Overview]
13
+ * link:conversion-pipeline.adoc[Conversion Pipeline]
14
+ * link:path-operations.adoc[Path Operations]
15
+ * link:coordinate-systems.adoc[Coordinate Systems]
16
+
17
+ == Concepts
18
+
19
+ **SVG Document**:: An XML document containing vector graphics defined by geometric shapes and paths.
20
+
21
+ **ViewBox**:: The coordinate system and viewport dimensions for SVG content.
22
+
23
+ **Path Data**:: A string of commands and coordinates defining vector paths in SVG.
24
+
25
+ **Transform Groups**:: SVG `<g>` elements with transform attributes for coordinate system adjustments.
26
+
27
+ **Optimization**:: Techniques to reduce SVG file size and improve rendering performance.
28
+
29
+ == SVG Document Structure
30
+
31
+ === Complete SVG Document
32
+
33
+ Postsvg generates well-formed, standards-compliant SVG documents:
34
+
35
+ .SVG Document Template
36
+ [source,xml]
37
+ ----
38
+ <?xml version="1.0" encoding="UTF-8"?>
39
+ <svg xmlns="http://www.w3.org/2000/svg"
40
+ width="WIDTH"
41
+ height="HEIGHT"
42
+ viewBox="LLX LLY WIDTH HEIGHT">
43
+ <defs>
44
+ <!-- Reusable definitions: patterns, gradients, clip paths -->
45
+ </defs>
46
+ <g transform="translate(0 HEIGHT) scale(1 -1)">
47
+ <!-- Graphics elements with Y-axis flip -->
48
+ </g>
49
+ </svg>
50
+ ----
51
+
52
+ === Document Components
53
+
54
+ **XML Declaration:**
55
+
56
+ [source,xml]
57
+ ----
58
+ <?xml version="1.0" encoding="UTF-8"?>
59
+ ----
60
+
61
+ Identifies the document as XML version 1.0 with UTF-8 encoding.
62
+
63
+ **SVG Root Element:**
64
+
65
+ [source,xml]
66
+ ----
67
+ <svg xmlns="http://www.w3.org/2000/svg"
68
+ width="612"
69
+ height="792"
70
+ viewBox="0 0 612 792">
71
+ ----
72
+
73
+ **Attributes:**
74
+
75
+ * `xmlns` - SVG namespace declaration (required)
76
+ * `width` - Physical width in pixels or units
77
+ * `height` - Physical height in pixels or units
78
+ * `viewBox` - User coordinate system (min-x min-y width height)
79
+
80
+ **Definitions Section:**
81
+
82
+ [source,xml]
83
+ ----
84
+ <defs>
85
+ <clipPath id="clipPath2">
86
+ <path d="M 50 50 L 350 50 L 350 350 L 50 350 Z"/>
87
+ </clipPath>
88
+ <pattern id="pattern1">...</pattern>
89
+ <linearGradient id="grad1">...</linearGradient>
90
+ </defs>
91
+ ----
92
+
93
+ Contains reusable graphics elements referenced by other elements.
94
+
95
+ **Transform Group:**
96
+
97
+ [source,xml]
98
+ ----
99
+ <g transform="translate(0 792) scale(1 -1)">
100
+ <!-- All path elements go here -->
101
+ </g>
102
+ ----
103
+
104
+ Applies coordinate system transformation to flip Y-axis from PostScript to SVG convention.
105
+
106
+ == ViewBox Configuration
107
+
108
+ === From BoundingBox to ViewBox
109
+
110
+ The PostScript BoundingBox comment defines the page dimensions:
111
+
112
+ [source,postscript]
113
+ ----
114
+ %%BoundingBox: llx lly urx ury
115
+ ----
116
+
117
+ This converts to SVG viewBox:
118
+
119
+ .BoundingBox to ViewBox Mapping
120
+ [source]
121
+ ----
122
+ PostScript BoundingBox: llx lly urx ury
123
+ ↓ ↓ ↓ ↓
124
+ SVG viewBox: min-x min-y width height
125
+
126
+ Where:
127
+ min-x = llx
128
+ min-y = lly
129
+ width = urx - llx
130
+ height = ury - lly
131
+ ----
132
+
133
+ .ViewBox Example
134
+ [example]
135
+ ====
136
+ **PostScript:**
137
+
138
+ [source,postscript]
139
+ ----
140
+ %%BoundingBox: 0 0 612 792
141
+ % US Letter page (8.5" × 11" at 72 DPI)
142
+ ----
143
+
144
+ **SVG:**
145
+
146
+ [source,xml]
147
+ ----
148
+ <svg width="612" height="792" viewBox="0 0 612 792">
149
+ ----
150
+
151
+ **Interpretation:**
152
+
153
+ * Coordinate system starts at (0, 0)
154
+ * Extends 612 units right, 792 units down
155
+ * 1 unit = 1 point = 1/72 inch
156
+ ====
157
+
158
+ === Non-Zero Origin
159
+
160
+ When BoundingBox doesn't start at origin:
161
+
162
+ .Offset BoundingBox
163
+ [example]
164
+ ====
165
+ **PostScript:**
166
+
167
+ [source,postscript]
168
+ ----
169
+ %%BoundingBox: 50 50 250 250
170
+ % 200×200 area, offset from origin
171
+ ----
172
+
173
+ **SVG Option 1: Preserve Origin**
174
+
175
+ [source,xml]
176
+ ----
177
+ <svg width="200" height="200" viewBox="50 50 200 200">
178
+ <!-- Content uses original coordinates (50-250) -->
179
+ </svg>
180
+ ----
181
+
182
+ **SVG Option 2: Normalize to Zero**
183
+
184
+ [source,xml]
185
+ ----
186
+ <svg width="200" height="200" viewBox="0 0 200 200">
187
+ <g transform="translate(-50 200) scale(1 -1) translate(0 -50)">
188
+ <!-- Content adjusted to (0-200) range -->
189
+ </g>
190
+ </svg>
191
+ ----
192
+ ====
193
+
194
+ == Coordinate System Transformation
195
+
196
+ === The Y-Axis Problem
197
+
198
+ PostScript and SVG use different Y-axis orientations:
199
+
200
+ .Coordinate System Comparison
201
+ [source]
202
+ ----
203
+ PostScript (Y ↑) SVG (Y ↓)
204
+
205
+ Height ┌───────┐ 0 0 ┌───────┐ 0
206
+ │ ● │ │ ● │
207
+ 0 └───────┘ Width Height └───────┘ Width
208
+
209
+ Point (100, 150): Point (100, 150):
210
+ - 150 units UP - 150 units DOWN
211
+ - Near TOP - Near BOTTOM
212
+ ----
213
+
214
+ === The Transform Solution
215
+
216
+ Postsvg uses a transform group to flip the Y-axis:
217
+
218
+ [source,xml]
219
+ ----
220
+ <g transform="translate(0 HEIGHT) scale(1 -1)">
221
+ <!-- PostScript graphics here -->
222
+ </g>
223
+ ----
224
+
225
+ **Transformation Steps:**
226
+
227
+ 1. **Translate (0, HEIGHT)** - Move origin to bottom-left
228
+ 2. **Scale (1, -1)** - Flip Y-axis (multiply Y by -1)
229
+
230
+ .Transform Breakdown
231
+ [example]
232
+ ====
233
+ Given height = 200:
234
+
235
+ [source,xml]
236
+ ----
237
+ <g transform="translate(0 200) scale(1 -1)">
238
+ ----
239
+
240
+ **Effect on Point (100, 50):**
241
+
242
+ [source]
243
+ ----
244
+ Original SVG: (100, 50)
245
+ After translate(0, 200): (100, 250)
246
+ After scale(1, -1): (100, -250)
247
+
248
+ But SVG renders this as if Y started at bottom,
249
+ so PostScript (100, 50) appears in correct position!
250
+ ----
251
+ ====
252
+
253
+ === Transform Limitations
254
+
255
+ The global Y-flip doesn't work for:
256
+
257
+ * Text (would be upside-down)
258
+ * Images (would be flipped)
259
+ * Patterns (may need special handling)
260
+
261
+ For these elements, additional transforms may be needed.
262
+
263
+ == Path Generation
264
+
265
+ === From Graphics Operations to SVG Paths
266
+
267
+ The [`SvgGenerator`](../../lib/postsvg/svg_generator.rb:7) converts path segments to SVG path data.
268
+
269
+ .Path Generation Flow
270
+ [source]
271
+ ----
272
+ PostScript Operations Graphics State SVG Path
273
+ ────────────────────────────────────────────────────────────────
274
+ newpath → current_path: []
275
+ 100 100 moveto → [(M 100,100)]
276
+ 200 100 lineto → [(M 100,100),
277
+ (L 200,100)]
278
+ 200 200 lineto → [(M 100,100),
279
+ (L 200,100),
280
+ (L 200,200)]
281
+ closepath → [(M 100,100),
282
+ (L 200,100),
283
+ (L 200,200),
284
+ (Z)]
285
+ stroke → Generate SVG → <path d="M 100 100
286
+ L 200 100
287
+ L 200 200
288
+ Z"
289
+ stroke="#000"
290
+ fill="none"/>
291
+ ----
292
+
293
+ === SVG Path Data Commands
294
+
295
+ PostScript path operators map to SVG path data commands:
296
+
297
+ .Command Mapping
298
+ [source]
299
+ ----
300
+ PostScript SVG Description
301
+ ─────────────────────────────────────────────────────
302
+ moveto x y → M x y Move to absolute position
303
+ rmoveto dx dy → m dx dy Move relative
304
+ lineto x y → L x y Line to absolute position
305
+ rlineto dx dy → l dx dy Line relative
306
+ curveto x1 y1 → C x1 y1 Cubic Bézier curve
307
+ x2 y2 x3 y3 x2 y2
308
+ x3 y3
309
+ rcurveto ... → c ... Relative cubic curve
310
+ closepath → Z Close path
311
+ ----
312
+
313
+ .Path Data Example
314
+ [example]
315
+ ====
316
+ **PostScript:**
317
+
318
+ [source,postscript]
319
+ ----
320
+ newpath
321
+ 50 50 moveto
322
+ 150 50 lineto
323
+ 100 150 lineto
324
+ closepath
325
+ fill
326
+ ----
327
+
328
+ **SVG Path Data:**
329
+
330
+ [source,xml]
331
+ ----
332
+ <path d="M 50 50 L 150 50 L 100 150 Z"
333
+ fill="#808080"
334
+ stroke="none"/>
335
+ ----
336
+ ====
337
+
338
+ === Path Data Optimization
339
+
340
+ The [`PathBuilder`](../../lib/postsvg/path_builder.rb:7) optimizes path data:
341
+
342
+ **Number Formatting:**
343
+
344
+ [source,ruby]
345
+ ----
346
+ def num_fmt(n)
347
+ # Remove trailing zeros
348
+ format("%.3f", n).sub(/\.?0+$/, "")
349
+ end
350
+
351
+ # Examples:
352
+ # 100.0 → "100"
353
+ # 50.500 → "50.5"
354
+ # 33.333 → "33.333"
355
+ ----
356
+
357
+ **Coordinate Precision:**
358
+
359
+ * Use 3 decimal places for precision
360
+ * Remove unnecessary trailing zeros
361
+ * Handle negative zero (convert to 0)
362
+
363
+ .Optimized Path Data
364
+ [example]
365
+ ====
366
+ **Before Optimization:**
367
+
368
+ [source,xml]
369
+ ----
370
+ <path d="M 100.000 100.000 L 200.000 100.000 L 200.000 200.000 Z"/>
371
+ ----
372
+
373
+ **After Optimization:**
374
+
375
+ [source,xml]
376
+ ----
377
+ <path d="M 100 100 L 200 100 L 200 200 Z"/>
378
+ ----
379
+
380
+ Saves 27 bytes (31% reduction)!
381
+ ====
382
+
383
+ == Styling and Attributes
384
+
385
+ === Stroke Operations
386
+
387
+ When a path is stroked, generate stroke attributes:
388
+
389
+ [source,xml]
390
+ ----
391
+ <path d="..."
392
+ fill="none"
393
+ stroke="#000000"
394
+ stroke-width="2"
395
+ stroke-linecap="round"
396
+ stroke-linejoin="miter"
397
+ stroke-dasharray="5,3"/>
398
+ ----
399
+
400
+ **Stroke Attributes:**
401
+
402
+ `stroke`:: Stroke color (hex RGB or color name)
403
+ `stroke-width`:: Line thickness in user units
404
+ `stroke-linecap`:: End cap style (butt, round, square)
405
+ `stroke-linejoin`:: Corner join style (miter, round, bevel)
406
+ `stroke-dasharray`:: Dash pattern (comma-separated lengths)
407
+ `stroke-miterlimit`:: Miter limit for sharp corners
408
+
409
+ .Stroke Example
410
+ [example]
411
+ ====
412
+ **PostScript:**
413
+
414
+ [source,postscript]
415
+ ----
416
+ newpath
417
+ 50 50 moveto
418
+ 350 50 lineto
419
+ 5 setlinewidth
420
+ 1 setlinecap
421
+ 1 setlinejoin
422
+ [10 5] 0 setdash
423
+ 1 0 0 setrgbcolor
424
+ stroke
425
+ ----
426
+
427
+ **SVG:**
428
+
429
+ [source,xml]
430
+ ----
431
+ <path d="M 50 50 L 350 50"
432
+ fill="none"
433
+ stroke="#FF0000"
434
+ stroke-width="5"
435
+ stroke-linecap="round"
436
+ stroke-linejoin="round"
437
+ stroke-dasharray="10,5"/>
438
+ ----
439
+ ====
440
+
441
+ === Fill Operations
442
+
443
+ When a path is filled, generate fill attributes:
444
+
445
+ [source,xml]
446
+ ----
447
+ <path d="..."
448
+ fill="#808080"
449
+ stroke="none"/>
450
+ ----
451
+
452
+ **Fill Attributes:**
453
+
454
+ `fill`:: Fill color (hex RGB or color name)
455
+ `fill-rule`:: Winding rule (nonzero or evenodd)
456
+ `fill-opacity`:: Opacity (0.0 to 1.0)
457
+
458
+ .Fill Example
459
+ [example]
460
+ ====
461
+ **PostScript:**
462
+
463
+ [source,postscript]
464
+ ----
465
+ newpath
466
+ 100 100 moveto
467
+ 200 100 lineto
468
+ 200 200 lineto
469
+ 100 200 lineto
470
+ closepath
471
+ 0.5 setgray
472
+ fill
473
+ ----
474
+
475
+ **SVG:**
476
+
477
+ [source,xml]
478
+ ----
479
+ <path d="M 100 100 L 200 100 L 200 200 L 100 200 Z"
480
+ fill="#808080"
481
+ stroke="none"/>
482
+ ----
483
+ ====
484
+
485
+ === Color Conversion
486
+
487
+ PostScript colors convert to SVG hex format:
488
+
489
+ .Color Conversion
490
+ [source]
491
+ ----
492
+ PostScript RGB SVG Hex
493
+ ──────────────────────────────────
494
+ 1 0 0 setrgbcolor → #FF0000 (red)
495
+ 0 1 0 setrgbcolor → #00FF00 (green)
496
+ 0 0 1 setrgbcolor → #0000FF (blue)
497
+ 0.5 setgray → #808080 (50% gray)
498
+ 0 setgray → #000000 (black)
499
+ 1 setgray → #FFFFFF (white)
500
+
501
+ Conversion formula:
502
+ hex = format("#%02x%02x%02x", r*255, g*255, b*255)
503
+ ----
504
+
505
+ .Color Examples
506
+ [example]
507
+ ====
508
+ [source,ruby]
509
+ ----
510
+ # Red
511
+ rgb_to_hex(1.0, 0.0, 0.0) # => "#ff0000"
512
+
513
+ # Orange
514
+ rgb_to_hex(1.0, 0.5, 0.0) # => "#ff8000"
515
+
516
+ # Gray
517
+ rgb_to_hex(0.5, 0.5, 0.5) # => "#808080"
518
+
519
+ # Implementation
520
+ def rgb_to_hex(r, g, b)
521
+ format("#%02x%02x%02x",
522
+ (r * 255).to_i,
523
+ (g * 255).to_i,
524
+ (b * 255).to_i)
525
+ end
526
+ ----
527
+ ====
528
+
529
+ == Clipping Paths
530
+
531
+ === ClipPath Generation
532
+
533
+ PostScript clip operations create SVG clipPath definitions:
534
+
535
+ .ClipPath Generation Flow
536
+ [source]
537
+ ----
538
+ PostScript SVG
539
+ ──────────────────────────────────────────────────
540
+ newpath
541
+ 50 50 moveto
542
+ 350 50 lineto
543
+ 350 350 lineto
544
+ 50 350 lineto
545
+ closepath
546
+ clip → <defs>
547
+ newpath <clipPath id="clipPath2">
548
+ <path d="M 50 50 L 350 50
549
+ L 350 350 L 50 350 Z"/>
550
+ </clipPath>
551
+ </defs>
552
+
553
+ % Subsequent drawing
554
+ 100 100 moveto
555
+ 200 200 lineto
556
+ stroke → <path d="M 100 100 L 200 200"
557
+ clip-path="url(#clipPath2)"
558
+ .../>
559
+ ----
560
+
561
+ === ClipPath Deduplication
562
+
563
+ Postsvg optimizes by reusing identical clip paths:
564
+
565
+ .ClipPath Cache
566
+ [example]
567
+ ====
568
+ [source,ruby]
569
+ ----
570
+ # In ExecutionContext
571
+ @clippath_cache = {}
572
+
573
+ # First clip path
574
+ clip_path_d = "M 50 50 L 350 350 Z"
575
+ clip_id = @clippath_cache[clip_path_d]
576
+
577
+ unless clip_id
578
+ # Create new clipPath
579
+ clip_id = next_clippath_id # => 2
580
+ @clippath_cache[clip_path_d] = clip_id
581
+ add_def("<clipPath id=\"clipPath#{clip_id}\">
582
+ <path d=\"#{clip_path_d}\"/>
583
+ </clipPath>")
584
+ end
585
+
586
+ # Second identical clip path
587
+ clip_path_d = "M 50 50 L 350 350 Z"
588
+ clip_id = @clippath_cache[clip_path_d] # => 2 (reused!)
589
+ # No new clipPath created
590
+ ----
591
+
592
+ **Benefits:**
593
+
594
+ * Reduces SVG file size
595
+ * Improves rendering performance
596
+ * Maintains clean, minimal output
597
+ ====
598
+
599
+ == SVG Generation Process
600
+
601
+ === Step-by-Step Generation
602
+
603
+ The generation process in [`SvgGenerator`](../../lib/postsvg/svg_generator.rb:7):
604
+
605
+ .Generation Steps
606
+ [source]
607
+ ----
608
+ 1. Initialize Generator
609
+ └─> Create empty paths array
610
+
611
+ 2. Execute PostScript
612
+ └─> Build paths in PathBuilder
613
+ └─> Convert to SVG path data
614
+ └─> Add to paths array
615
+
616
+ 3. Generate SVG Document
617
+ ├─> Create XML declaration
618
+ ├─> Create <svg> root
619
+ │ ├─> Set width, height, viewBox
620
+ │ └─> Add namespace
621
+ ├─> Create <defs> section
622
+ │ └─> Add clipPaths, patterns, etc.
623
+ ├─> Create transform <g>
624
+ │ └─> Add coordinate flip transform
625
+ └─> Add path elements
626
+ └─> With appropriate attributes
627
+
628
+ 4. Format and Output
629
+ └─> Pretty-print XML
630
+ └─> Return SVG string
631
+ ----
632
+
633
+ === Implementation Example
634
+
635
+ .SVG Generation Code
636
+ [example]
637
+ ====
638
+ [source,ruby]
639
+ ----
640
+ # Initialize generator
641
+ generator = SvgGenerator.new
642
+
643
+ # Add paths during interpretation
644
+ generator.add_path(
645
+ path_segments,
646
+ graphics_state,
647
+ :stroke # or :fill
648
+ )
649
+
650
+ # Generate final SVG
651
+ svg = generator.generate(
652
+ width: bbox[:width],
653
+ height: bbox[:height],
654
+ viewbox: "#{bbox[:llx]} #{bbox[:lly]} " \
655
+ "#{bbox[:width]} #{bbox[:height]}"
656
+ )
657
+
658
+ # Output:
659
+ # <?xml version="1.0" encoding="UTF-8"?>
660
+ # <svg xmlns="http://www.w3.org/2000/svg"
661
+ # width="612" height="792"
662
+ # viewBox="0 0 612 792">
663
+ # <g transform="translate(0 792) scale(1 -1)">
664
+ # <path d="..." .../>
665
+ # </g>
666
+ # </svg>
667
+ ----
668
+ ====
669
+
670
+ == Optimization Techniques
671
+
672
+ === Path Data Minimization
673
+
674
+ **Remove Redundancy:**
675
+
676
+ [source,xml]
677
+ ----
678
+ <!-- Before: Redundant moveto -->
679
+ <path d="M 100 100 M 100 100 L 200 200"/>
680
+
681
+ <!-- After: Single moveto -->
682
+ <path d="M 100 100 L 200 200"/>
683
+ ----
684
+
685
+ **Combine Consecutive Commands:**
686
+
687
+ [source,xml]
688
+ ----
689
+ <!-- Before: Multiple lineto -->
690
+ <path d="M 100 100 L 150 100 L 200 100 L 250 100"/>
691
+
692
+ <!-- After: Implicit lineto -->
693
+ <path d="M 100 100 L 150 100 200 100 250 100"/>
694
+ ----
695
+
696
+ **Use Relative Commands:**
697
+
698
+ [source,xml]
699
+ ----
700
+ <!-- Before: Absolute coordinates -->
701
+ <path d="M 100 100 L 110 100 L 120 100 L 130 100"/>
702
+
703
+ <!-- After: Relative coordinates (shorter) -->
704
+ <path d="M 100 100 l 10 0 10 0 10 0"/>
705
+ ----
706
+
707
+ === Attribute Optimization
708
+
709
+ **Omit Default Values:**
710
+
711
+ [source,xml]
712
+ ----
713
+ <!-- Before: Include defaults -->
714
+ <path d="..."
715
+ stroke-width="1"
716
+ stroke-linecap="butt"
717
+ stroke-linejoin="miter"/>
718
+
719
+ <!-- After: Omit defaults (1, butt, miter) -->
720
+ <path d="..."/>
721
+ ----
722
+
723
+ **Use Style Attribute:**
724
+
725
+ [source,xml]
726
+ ----
727
+ <!-- Before: Multiple attributes -->
728
+ <path d="..."
729
+ fill="#ff0000"
730
+ stroke="#000000"
731
+ stroke-width="2"/>
732
+
733
+ <!-- After: Style attribute (sometimes smaller) -->
734
+ <path d="..."
735
+ style="fill:#ff0000;stroke:#000;stroke-width:2"/>
736
+ ----
737
+
738
+ === Structural Optimization
739
+
740
+ **Group Common Attributes:**
741
+
742
+ [source,xml]
743
+ ----
744
+ <!-- Before: Repeated attributes -->
745
+ <path d="..." fill="#ff0000" stroke="none"/>
746
+ <path d="..." fill="#ff0000" stroke="none"/>
747
+ <path d="..." fill="#ff0000" stroke="none"/>
748
+
749
+ <!-- After: Group with shared style -->
750
+ <g fill="#ff0000" stroke="none">
751
+ <path d="..."/>
752
+ <path d="..."/>
753
+ <path d="..."/>
754
+ </g>
755
+ ----
756
+
757
+ == Advanced SVG Features
758
+
759
+ === Gradients and Patterns
760
+
761
+ While Postsvg focuses on basic PostScript conversion, SVG supports advanced features:
762
+
763
+ **Linear Gradients:**
764
+
765
+ [source,xml]
766
+ ----
767
+ <defs>
768
+ <linearGradient id="grad1">
769
+ <stop offset="0%" stop-color="#ff0000"/>
770
+ <stop offset="100%" stop-color="#0000ff"/>
771
+ </linearGradient>
772
+ </defs>
773
+ <path d="..." fill="url(#grad1)"/>
774
+ ----
775
+
776
+ **Patterns:**
777
+
778
+ [source,xml]
779
+ ----
780
+ <defs>
781
+ <pattern id="pattern1" width="20" height="20"
782
+ patternUnits="userSpaceOnUse">
783
+ <circle cx="10" cy="10" r="5" fill="#000"/>
784
+ </pattern>
785
+ </defs>
786
+ <path d="..." fill="url(#pattern1)"/>
787
+ ----
788
+
789
+ === Transparency and Blending
790
+
791
+ SVG Level 3 features:
792
+
793
+ **Opacity:**
794
+
795
+ [source,xml]
796
+ ----
797
+ <path d="..." fill="#ff0000" opacity="0.5"/>
798
+ <path d="..." fill="#ff0000" fill-opacity="0.5"/>
799
+ ----
800
+
801
+ **Blend Modes:**
802
+
803
+ [source,xml]
804
+ ----
805
+ <path d="..." style="mix-blend-mode: multiply"/>
806
+ ----
807
+
808
+ == Output Quality
809
+
810
+ === Validation
811
+
812
+ Ensure generated SVG is valid:
813
+
814
+ **XML Well-Formedness:**
815
+
816
+ * Properly nested elements
817
+ * Quoted attribute values
818
+ * Escaped special characters (&, <, >, ", ')
819
+
820
+ **SVG Compliance:**
821
+
822
+ * Valid element names
823
+ * Valid attribute values
824
+ * Proper namespace declarations
825
+
826
+ .XML Escaping
827
+ [example]
828
+ ====
829
+ [source,ruby]
830
+ ----
831
+ def escape_xml(str)
832
+ str.to_s
833
+ .gsub("&", "&amp;")
834
+ .gsub("<", "&lt;")
835
+ .gsub(">", "&gt;")
836
+ .gsub('"', "&quot;")
837
+ .gsub("'", "&#39;")
838
+ end
839
+
840
+ # Usage in text elements
841
+ text = "x < y && y > z"
842
+ escaped = escape_xml(text)
843
+ # => "x &lt; y &amp;&amp; y &gt; z"
844
+ ----
845
+ ====
846
+
847
+ === Browser Compatibility
848
+
849
+ Generated SVG works in all modern browsers:
850
+
851
+ * Chrome/Edge (Blink)
852
+ * Firefox (Gecko)
853
+ * Safari (WebKit)
854
+
855
+ **Best Practices:**
856
+
857
+ * Use standard SVG 1.1 features
858
+ * Avoid browser-specific extensions
859
+ * Test in multiple browsers
860
+
861
+ == Performance Considerations
862
+
863
+ === Generation Performance
864
+
865
+ **Time Complexity:**
866
+
867
+ * Path building: O(n) where n = number of path operators
868
+ * SVG generation: O(m) where m = number of paths
869
+ * Overall: O(n + m) - linear time
870
+
871
+ **Space Complexity:**
872
+
873
+ * Path storage: O(p) where p = path data size
874
+ * Output buffer: O(s) where s = SVG document size
875
+
876
+ === Rendering Performance
877
+
878
+ **SVG Rendering:**
879
+
880
+ * Simple paths: Fast
881
+ * Complex paths: May be slow
882
+ * Many paths: Group by style
883
+
884
+ **Optimization:**
885
+
886
+ * Minimize path complexity
887
+ * Reduce number of elements
888
+ * Use appropriate precision
889
+
890
+ ## Troubleshooting
891
+
892
+ === Common Issues
893
+
894
+ **Upside-Down Graphics:**
895
+
896
+ [source,xml]
897
+ ----
898
+ <!-- Missing transform -->
899
+ <svg><path d="..."/></svg>
900
+
901
+ <!-- Add Y-flip transform -->
902
+ <svg>
903
+ <g transform="translate(0 height) scale(1 -1)">
904
+ <path d="..."/>
905
+ </g>
906
+ </svg>
907
+ ----
908
+
909
+ **Invisible Paths:**
910
+
911
+ * Check viewBox matches content coordinates
912
+ * Verify stroke/fill attributes
913
+ * Ensure path data is complete
914
+
915
+ **Invalid SVG:**
916
+
917
+ * Validate XML syntax
918
+ * Check namespace declaration
919
+ * Verify attribute values
920
+
921
+ == Next Steps
922
+
923
+ * Review link:path-operations.adoc[Path Operations] for path construction
924
+ * Explore link:coordinate-systems.adoc[Coordinate Systems] for transforms
925
+ * See link:conversion-pipeline.adoc[Conversion Pipeline] for full process
926
+ * Check link:../architecture/generator-stage.adoc[Generator Architecture]
927
+ * Read link:../api-reference.adoc[API Reference] for implementation details
928
+
929
+ == Bibliography
930
+
931
+ * link:path-operations.adoc[Path Operations]
932
+ * link:coordinate-systems.adoc[Coordinate Systems]
933
+ * link:conversion-pipeline.adoc[Conversion Pipeline]
934
+ * link:../architecture/generator-stage.adoc[Generator Stage Architecture]
935
+ * link:https://www.w3.org/TR/SVG/[SVG Specification]
936
+ * link:https://www.w3.org/TR/SVG/paths.html[SVG Paths Specification]
937
+ * link:https://developer.mozilla.org/en-US/docs/Web/SVG[MDN SVG Documentation]