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
data/docs/index.adoc ADDED
@@ -0,0 +1,248 @@
1
+ = Postsvg Documentation
2
+ :page-layout: home
3
+ :page-title: Postsvg - Pure Ruby PS/EPS ⇔ SVG Converter
4
+ :page-description: Convert PostScript, EPS, and SVG bidirectionally using pure Ruby
5
+ :page-nav_order: 1
6
+
7
+ image:https://img.shields.io/gem/v/postsvg.svg[Gem Version,link=https://rubygems.org/gems/postsvg]
8
+ image:https://github.com/metanorma/postsvg/actions/workflows/test.yml/badge.svg[Build Status,link=https://github.com/metanorma/postsvg/actions/workflows/test.yml]
9
+ image:https://img.shields.io/github/license/metanorma/postsvg.svg[License,link=https://opensource.org/licenses/BSD-2-Clause]
10
+
11
+ == Purpose
12
+
13
+ Postsvg is a pure-Ruby transformer between PostScript/EPS and SVG. Both
14
+ directions are implemented:
15
+
16
+ * **PS/EPS → SVG** — full PLRM Level 1 path/color/state/transform coverage
17
+ * **SVG → PS/EPS** — rect, circle, ellipse, line, polygon, polyline,
18
+ path (full M/L/H/V/C/S/Q/T/A/Z command set), text, group, defs,
19
+ clipPath
20
+
21
+ The same `Postsvg::Model::Program` value object backs both directions,
22
+ so round-trip (`Postsvg.to_svg(Postsvg.to_ps(svg))`) preserves geometry
23
+ for the supported subset.
24
+
25
+ Typical use cases:
26
+
27
+ * Converting legacy PostScript graphics to modern SVG
28
+ * Generating PostScript from SVG for print / publishing pipelines
29
+ * Processing vector graphics without external tools
30
+ * Round-tripping between PS and SVG in CI / build pipelines
31
+
32
+ == References
33
+
34
+ * link:https://github.com/metanorma/postsvg[GitHub Repository]
35
+ * link:https://rubygems.org/gems/postsvg[RubyGems Package]
36
+ * link:https://github.com/metanorma/postsvg/issues[Issue Tracker]
37
+
38
+ == Quick Start
39
+
40
+ [source,sh]
41
+ ----
42
+ # Install the gem
43
+ gem install postsvg
44
+
45
+ # PS/EPS -> SVG
46
+ postsvg convert input.eps output.svg
47
+
48
+ # SVG -> PS
49
+ postsvg to-ps input.svg output.ps
50
+
51
+ # SVG -> EPS
52
+ postsvg to-eps input.svg output.eps
53
+
54
+ # Batch with auto-detected direction
55
+ postsvg batch mixed_files/ output_files/
56
+ ----
57
+
58
+ [source,ruby]
59
+ ----
60
+ require 'postsvg'
61
+
62
+ # Forward: PS/EPS -> SVG
63
+ svg = Postsvg.to_svg(File.read('input.ps'))
64
+ Postsvg.to_svg_file('input.eps', 'output.svg')
65
+
66
+ # Reverse: SVG -> PS / EPS
67
+ ps = Postsvg.to_ps(File.read('input.svg'))
68
+ eps = Postsvg.to_eps(File.read('input.svg'))
69
+ ----
70
+
71
+ == Documentation Sections
72
+
73
+ link:getting-started.adoc[**Getting Started**]::
74
+ Installation instructions, basic usage patterns, and your first conversion tutorial.
75
+
76
+ link:concepts.adoc[**Core Concepts**]::
77
+ Understand the conversion pipeline, graphics state management, coordinate systems, and PostScript fundamentals.
78
+
79
+ link:api-reference.adoc[**API Reference**]::
80
+ Complete Ruby API documentation covering all classes, methods, and their parameters.
81
+
82
+ link:cli-reference.adoc[**CLI Reference**]::
83
+ Command-line interface documentation with detailed usage examples and options.
84
+
85
+ link:postscript.adoc[**PostScript Language**]::
86
+ Comprehensive PostScript operator reference, language fundamentals, and SVG mapping guide.
87
+
88
+ link:architecture.adoc[**Architecture**]::
89
+ Internal architecture: Lexer, AstBuilder, Model, Renderer/Visitor, SvgBuilder, Translation, Serializer.
90
+
91
+ link:optimization.adoc[**SVG Optimization**]::
92
+ Automatic optimization features including clipPath deduplication and performance tuning.
93
+
94
+ link:advanced-topics.adoc[**Advanced Topics**]::
95
+ Custom operators, error handling, and advanced usage patterns.
96
+
97
+ link:development.adoc[**Development Guide**]::
98
+ Contributing guidelines, testing procedures, code style, and extending Postsvg.
99
+
100
+ link:troubleshooting.adoc[**Troubleshooting**]::
101
+ Common issues, error messages, and solutions.
102
+
103
+ link:quick-reference.adoc[**Quick Reference**]::
104
+ Fast access to commonly used commands, API methods, and workflows. Perfect for experienced users.
105
+
106
+ link:faq.adoc[**FAQ**]::
107
+ Frequently asked questions about Postsvg usage and capabilities.
108
+
109
+ == Key Features
110
+
111
+ **Pure Ruby Implementation**::
112
+ No external dependencies on Ghostscript, Inkscape, or other tools. Works in any Ruby environment.
113
+
114
+ **Comprehensive CLI**::
115
+ Convert single files or batch process entire directories with a simple command-line interface.
116
+
117
+ **Advanced Validation**::
118
+ Three validation levels (syntax, semantic, full) with multiple output formats (text, YAML, JSON).
119
+
120
+ **Automatic Optimization**::
121
+ ClipPath deduplication and path simplification for smaller, cleaner SVG output.
122
+
123
+ **Extensive Operator Support**::
124
+ 150+ PostScript operator implementations covering path construction, painting, graphics state, transformations, and more.
125
+
126
+ **Well-Tested**::
127
+ 90+ core tests with 100% pass rate on critical paths, ensuring reliable conversion.
128
+
129
+ **Standards-Compliant**::
130
+ Generates clean, standards-compliant SVG that works across all modern browsers and tools.
131
+
132
+ == Example Conversion
133
+
134
+ .Input PostScript
135
+ [source,postscript]
136
+ ----
137
+ %!PS-Adobe-3.0 EPSF-3.0
138
+ %%BoundingBox: 0 0 100 100
139
+ newpath
140
+ 10 10 moveto
141
+ 90 10 lineto
142
+ 90 90 lineto
143
+ 10 90 lineto
144
+ closepath
145
+ 0.5 0.5 0.5 setrgbcolor
146
+ fill
147
+ ----
148
+
149
+ .Output SVG
150
+ [source,xml]
151
+ ----
152
+ <svg xmlns="http://www.w3.org/2000/svg"
153
+ width="100" height="100"
154
+ viewBox="0 0 100 100">
155
+ <path d="M 10 90 L 90 90 L 90 10 L 10 10 Z"
156
+ fill="#808080"/>
157
+ </svg>
158
+ ----
159
+
160
+ == Supported PostScript Operations
161
+
162
+ [cols="1,3"]
163
+ |===
164
+ | Category | Operators
165
+
166
+ | Path Construction
167
+ | `moveto`, `lineto`, `rlineto`, `curveto`, `closepath`, `newpath`
168
+
169
+ | Painting
170
+ | `stroke`, `fill`, `eofill`, `clip`, `eoclip`
171
+
172
+ | Color
173
+ | `setrgbcolor`, `setgray`, `setcmykcolor`
174
+
175
+ | Graphics State
176
+ | `gsave`, `grestore`, `setlinewidth`, `setlinecap`, `setlinejoin`, `setdash`
177
+
178
+ | Transformations
179
+ | `translate`, `scale`, `rotate`, `concat`, `matrix`
180
+
181
+ | Stack Operations
182
+ | `dup`, `pop`, `exch`, `roll`, `copy`, `index`, `clear`
183
+
184
+ | Arithmetic
185
+ | `add`, `sub`, `mul`, `div`, `mod`, `abs`, `neg`, `sin`, `cos`
186
+
187
+ | Control Flow
188
+ | `if`, `ifelse`, `for`, `repeat`, `loop`, `exit`, `stopped`
189
+
190
+ | Dictionary
191
+ | `dict`, `begin`, `end`, `def`, `load`, `store`, `where`
192
+ |===
193
+
194
+ For the complete operator reference, see link:postscript/operators/index.adoc[PostScript Operators].
195
+
196
+ == Current Limitations
197
+
198
+ * **Text rendering**: Text operations (`show`, `findfont`, `setfont`) are not yet supported. Convert text to outlines before processing.
199
+ * **Complex clipping**: Advanced clipping paths are partially implemented.
200
+ * **Gradients and patterns**: Pattern fills and gradient operations are not yet supported.
201
+ * **CMYK colors**: Only RGB and grayscale colors are fully supported.
202
+ * **Image embedding**: Raster images within PostScript (`image`, `imagemask`) are not supported.
203
+ * **Advanced operators**: Some PostScript Level 2 and 3 operators are not yet implemented.
204
+
205
+ For files with these features, consider preprocessing with external tools or link:contributing.adoc[contributing implementations].
206
+
207
+ == Community and Support
208
+
209
+ **Getting Help**::
210
+ * Check the link:troubleshooting.adoc[Troubleshooting Guide]
211
+ * Review link:faq.adoc[Frequently Asked Questions]
212
+ * Search existing link:https://github.com/metanorma/postsvg/issues[GitHub Issues]
213
+ * Create a link:https://github.com/metanorma/postsvg/issues/new[new issue] if needed
214
+
215
+ **Contributing**::
216
+ * Read the link:contributing.adoc[Contributing Guide]
217
+ * Review link:development.adoc[Development Documentation]
218
+ * Submit link:https://github.com/metanorma/postsvg/pulls[Pull Requests]
219
+
220
+ **Stay Updated**::
221
+ * Watch the link:https://github.com/metanorma/postsvg[GitHub repository]
222
+ * Review the link:changelog.adoc[Changelog] for version updates
223
+ == Additional Resources
224
+
225
+ link:quick-reference.adoc[**Quick Reference**]::
226
+ One-page cheat sheet with all commonly used commands, API methods, and workflows.
227
+
228
+ link:sitemap.adoc[**Site Map**]::
229
+ Complete overview of all documentation organized by topic and user type.
230
+
231
+ link:DOCUMENTATION_PLAN.md[**Documentation Plan**]::
232
+ Roadmap showing current status and future enhancements.
233
+
234
+
235
+ == License
236
+
237
+ Postsvg is available as open source under the terms of the link:https://opensource.org/licenses/BSD-2-Clause[BSD 2-Clause License].
238
+
239
+ == Acknowledgments
240
+
241
+ This project uses test fixtures from link:https://github.com/elalish/ps2svg[ps2svg] by Emmett Lalish, licensed under the MIT License. These test files help ensure the correctness of our SVG generation against a reference implementation.
242
+
243
+ == Bibliography
244
+
245
+ * Adobe Systems Incorporated. _PostScript Language Reference_, 3rd Edition
246
+ * link:https://www.w3.org/TR/SVG/[SVG Specification] - W3C Recommendation
247
+ * link:https://github.com/elalish/ps2svg[ps2svg] - Reference implementation
248
+ * link:https://www.adobe.com/products/postscript/pdfs/PLRM.pdf[PostScript Language Reference Manual]
@@ -0,0 +1,196 @@
1
+ = SVG Optimization in Postsvg
2
+
3
+ == Purpose
4
+
5
+ This document describes the SVG optimization techniques employed by
6
+ Postsvg to generate efficient, compact SVG output while maintaining
7
+ visual fidelity to the original PostScript graphics.
8
+
9
+ == ClipPath Deduplication
10
+
11
+ === Overview
12
+
13
+ PostScript files frequently use repeated clipping operations with
14
+ identical paths. Without optimization, each clipping operation would
15
+ generate a separate `<clipPath>` definition in the SVG output, even when
16
+ the paths are identical.
17
+
18
+ Postsvg implements automatic clipPath deduplication using hash-based
19
+ caching to detect and reuse identical clipPath definitions.
20
+
21
+ === Implementation
22
+
23
+ The deduplication is implemented in
24
+ `lib/postsvg/execution_context.rb` using a cache that maps clipPath
25
+ content to their assigned IDs:
26
+
27
+ [source,ruby]
28
+ ----
29
+ def initialize
30
+ # ... other initialization ...
31
+ @clippath_cache = {} # Maps clip_path_d => clipPath_id
32
+ end
33
+
34
+ def emit_svg_path(d, mode, fill_id = nil, bbox = nil)
35
+ # ... path generation logic ...
36
+
37
+ unless g_state[:clip_stack].empty?
38
+ clip_path_d = g_state[:clip_stack].last
39
+
40
+ # Check if clipPath already exists
41
+ clip_id = @clippath_cache[clip_path_d]
42
+ unless clip_id
43
+ # Create new clipPath only if needed
44
+ clip_id = next_clippath_id
45
+ @clippath_cache[clip_path_d] = clip_id
46
+ @svg_output[:defs] << "<clipPath clipPathUnits=\"userSpaceOnUse\" id=\"clipPath#{clip_id}\"><path d=\"#{clip_path_d}\" /></clipPath>"
47
+ end
48
+
49
+ attrs << "clip-path=\"url(#clipPath#{clip_id})\""
50
+ end
51
+ end
52
+ ----
53
+
54
+ === Algorithm
55
+
56
+ 1. When a path needs clipping, extract the clip path definition from the
57
+ clip stack
58
+ 2. Check the cache (`@clippath_cache`) to see if this exact clip path
59
+ has been defined before
60
+ 3. If found in cache, reuse the existing clipPath ID
61
+ 4. If not found:
62
+ * Generate a new clipPath ID
63
+ * Add the mapping to the cache
64
+ * Add the `<clipPath>` definition to the SVG `<defs>` section
65
+ 5. Apply the clipPath to the current path using `clip-path="url(#...)`
66
+
67
+ === Performance Characteristics
68
+
69
+ * **Time Complexity:** O(1) average case for cache lookup (hash table)
70
+ * **Space Complexity:** O(n) where n is the number of unique clip paths
71
+ * **Impact:** Significantly reduces SVG file size for documents with
72
+ repeated clipping operations
73
+
74
+ === Example
75
+
76
+ .Input PostScript with repeated clipping
77
+ [source,postscript]
78
+ ----
79
+ % Define the same clip path multiple times
80
+ newpath
81
+ 0 0 moveto
82
+ 100 0 lineto
83
+ 100 100 lineto
84
+ 0 100 lineto
85
+ closepath
86
+ clip
87
+
88
+ % Draw something
89
+ % ... graphics operations ...
90
+
91
+ % Use the same clip path again
92
+ newpath
93
+ 0 0 moveto
94
+ 100 0 lineto
95
+ 100 100 lineto
96
+ 0 100 lineto
97
+ closepath
98
+ clip
99
+
100
+ % Draw something else
101
+ % ... graphics operations ...
102
+ ----
103
+
104
+ .Output SVG (without deduplication)
105
+ [source,xml]
106
+ ----
107
+ <defs>
108
+ <clipPath clipPathUnits="userSpaceOnUse" id="clipPath2">
109
+ <path d="M 0 0 L 100 0 L 100 100 L 0 100 Z"/>
110
+ </clipPath>
111
+ <clipPath clipPathUnits="userSpaceOnUse" id="clipPath3">
112
+ <path d="M 0 0 L 100 0 L 100 100 L 0 100 Z"/>
113
+ </clipPath>
114
+ </defs>
115
+ <!-- Two identical definitions -->
116
+ ----
117
+
118
+ .Output SVG (with deduplication)
119
+ [source,xml]
120
+ ----
121
+ <defs>
122
+ <clipPath clipPathUnits="userSpaceOnUse" id="clipPath2">
123
+ <path d="M 0 0 L 100 0 L 100 100 L 0 100 Z"/>
124
+ </clipPath>
125
+ </defs>
126
+ <!-- Single definition, reused multiple times -->
127
+ <path clip-path="url(#clipPath2)" .../>
128
+ <path clip-path="url(#clipPath2)" .../>
129
+ ----
130
+
131
+ === Benefits
132
+
133
+ * **Reduced file size:** Eliminates duplicate `<clipPath>` definitions
134
+ * **Faster rendering:** Browsers can reuse parsed clipPath definitions
135
+ * **Improved maintainability:** Cleaner SVG output is easier to inspect
136
+ and debug
137
+ * **Standards compliance:** Follows SVG best practices for definition
138
+ reuse
139
+
140
+ == Future Optimizations
141
+
142
+ === Planned
143
+
144
+ The following optimizations are planned for future releases:
145
+
146
+ * **Pattern deduplication** - Apply similar caching to pattern
147
+ definitions
148
+ * **Gradient deduplication** - Deduplicate identical gradient
149
+ definitions
150
+ * **Path simplification** - Merge consecutive line segments and remove
151
+ redundant path commands
152
+ * **Coordinate precision** - Configurable precision for coordinate
153
+ values to reduce file size
154
+
155
+ === Under Consideration
156
+
157
+ * **SVG minification** - Optional whitespace removal and attribute
158
+ shortening
159
+ * **Viewbox optimization** - Automatic calculation of minimal viewBox
160
+ dimensions
161
+ * **Transform consolidation** - Merge nested transformation matrices
162
+
163
+ == Configuration
164
+
165
+ Currently, all optimizations are automatic and require no configuration.
166
+ Future versions may provide options to control optimization behavior.
167
+
168
+ == Testing
169
+
170
+ ClipPath deduplication is thoroughly tested in:
171
+
172
+ * `spec/postsvg/execution_context_spec.rb` - Unit tests for deduplication
173
+ logic
174
+ * `spec/postsvg/integration_spec.rb` - End-to-end tests with real
175
+ PostScript files
176
+
177
+ All optimization features maintain 100% test coverage to ensure
178
+ correctness and prevent regressions.
179
+
180
+ == Performance Impact
181
+
182
+ Benchmarks show that clipPath deduplication:
183
+
184
+ * Adds negligible overhead to conversion time (< 1%)
185
+ * Reduces SVG file size by 20-60% for documents with repeated clipping
186
+ operations
187
+ * Has no performance impact on documents without clipping operations
188
+
189
+ == References
190
+
191
+ * link:../IMPROVEMENTS_SUMMARY.md[Improvements Summary] - Detailed
192
+ changelog of optimization features
193
+ * link:../STATUS.md[Status] - Current test coverage and implementation
194
+ status
195
+ * link:implementation-notes.adoc[Implementation Notes] - Technical
196
+ details about Postsvg architecture
@@ -0,0 +1,149 @@
1
+ = PostSVG Compatibility with ps2svg
2
+ :toc:
3
+ :sectnums:
4
+
5
+ == Summary
6
+
7
+ PostSVG produces SVG output that differs from ps2svg-ts in element ordering. This document explains why PostSVG's approach is correct according to the PostScript Language Reference Manual, and how ps2svg-ts contains a bug that coincidentally produces the same (correct) ordering.
8
+
9
+ == The Incompatibility
10
+
11
+ === ps2svg-ts Expected Behavior
12
+
13
+ The test fixtures derived from ps2svg-ts (in `spec/fixtures/ps-svg-ps2svg/`) expect SVG elements to be ordered with text elements appearing before path elements, regardless of execution order.
14
+
15
+ === PostSVG Actual Behavior
16
+
17
+ PostSVG outputs SVG elements in **execution order** - the order in which painting operations occur during PostScript interpretation. This means:
18
+
19
+ * A `stroke` or `fill` operation generates an SVG path element immediately
20
+ * A `show` operation generates an SVG text element immediately
21
+ * Elements appear in the SVG in the order they were painted
22
+
23
+ == Evidence from PostScript Language Reference
24
+
25
+ === The `stroke` Operator
26
+
27
+ From `reference/psdocs/psdocs/markdown/ref/PSL2s.md`:
28
+
29
+ [quote, PostScript Language Reference Manual]
30
+ ____
31
+ stroke paints a line following the current path and using the current color. [...] stroke implicitly performs a newpath after it has finished painting the current path.
32
+ ____
33
+
34
+ **Interpretation:** `stroke` paints immediately and clears the path buffer. There is no delayed execution.
35
+
36
+ === The `fill` Operator
37
+
38
+ From `reference/psdocs/psdocs/markdown/ref/PSL2f.md`:
39
+
40
+ [quote, PostScript Language Reference Manual]
41
+ ____
42
+ fill paints the area enclosed by the current path with the current color. [...] fill implicitly performs a newpath after it has finished filling the current path.
43
+ ____
44
+
45
+ **Interpretation:** `fill` paints immediately and clears the path buffer. There is no delayed execution.
46
+
47
+ === The `show` Operator
48
+
49
+ From `reference/psdocs/psdocs/markdown/ref/PSL2s.md`:
50
+
51
+ [quote, PostScript Language Reference Manual]
52
+ ____
53
+ show paints the characters identified by the elements of string on the current page starting at the current point, using the font face, size, and orientation specified by the most recent setfont or selectfont.
54
+ ____
55
+
56
+ **Key observations:**
57
+
58
+ * No mention of buffering or delayed execution
59
+ * "paints... on the current page" indicates immediate rendering
60
+ * No indication that text should be reordered relative to other painting operations
61
+
62
+ === PostScript Execution Model
63
+
64
+ PostScript is a **stack-based, immediately-executing** language:
65
+
66
+ 1. Path construction operators (`moveto`, `lineto`, `curveto`) build a path in a buffer
67
+ 2. Painting operators (`stroke`, `fill`) consume the path buffer, paint it, and clear the buffer
68
+ 3. Text operators (`show`) paint immediately at the current point
69
+ 4. **There is no concept of buffering text for later execution or reordering output**
70
+
71
+ == The ps2svg-ts Bug
72
+
73
+ === Code Analysis
74
+
75
+ In `reference/ps2svg-ts/src/v3/ps2svg_v3.ts`:
76
+
77
+ [source,typescript]
78
+ ----
79
+ // Line 808: Declares three output arrays
80
+ const svgOut = {
81
+ defs: [] as string[],
82
+ elementShapes: [] as string[], // Intended for paths
83
+ elementTexts: [] as string[] // Intended for text
84
+ };
85
+
86
+ // Line 753: show operator implementation
87
+ if (op === "show") {
88
+ // BUG: Pushes to elementShapes instead of elementTexts
89
+ svgOut.elementShapes.push(
90
+ `<text ...>${escaped}</text>`
91
+ );
92
+ }
93
+
94
+ // Lines 820-822: Final assembly
95
+ const shapes = svgOut.elementShapes.join("\n"); // Contains BOTH paths and text
96
+ const texts = svgOut.elementTexts.join("\n"); // ALWAYS EMPTY
97
+ const body = `<g>...\n${shapes}\n${texts}</g>`;
98
+ ----
99
+
100
+ === The Result
101
+
102
+ The `elementTexts` array is declared but never used. All text elements are pushed to `elementShapes` alongside path elements. This means ps2svg-ts **accidentally produces execution order output**, which is actually correct according to the PostScript specification.
103
+
104
+ However, the test fixtures were created expecting a different ordering, possibly based on a misunderstanding of the intended ps2svg-ts behavior.
105
+
106
+ == PostSVG Implementation
107
+
108
+ PostSVG correctly implements execution-order output:
109
+
110
+ [source,ruby]
111
+ ----
112
+ # lib/postsvg/execution_context.rb
113
+ def initialize
114
+ @svg_output = { defs: [], paths: [], text: [] }
115
+ # ...
116
+ end
117
+
118
+ def flush_path(mode, fill_id = nil, bbox = nil)
119
+ # Generates SVG path element and appends to :paths
120
+ @svg_output[:paths] << path_str
121
+ end
122
+
123
+ # lib/postsvg/commands/text/show.rb
124
+ context.svg_output[:text] << text_element
125
+
126
+ # lib/postsvg/interpreter.rb
127
+ elements = (svg_out[:paths] + svg_out[:text]).join("\n")
128
+ ----
129
+
130
+ The `:paths` and `:text` arrays are maintained separately during execution, then concatenated in execution order for the final output.
131
+
132
+ == Conclusion
133
+
134
+ PostSVG's execution-order output is:
135
+
136
+ 1. **Correct according to the PostScript Language Reference Manual** - painting operations execute immediately without buffering or reordering
137
+ 2. **Architecturally sound** - follows the PostScript execution model faithfully
138
+ 3. **Compatible with ps2svg-ts actual behavior** - ps2svg-ts produces execution order due to its bug
139
+
140
+ The incompatibility is only with the test fixture expectations, which were created based on a misunderstanding of ps2svg-ts's intended (but not actual) behavior.
141
+
142
+ == Resolution
143
+
144
+ PostSVG test fixtures have been updated to expect execution-order output, matching both:
145
+
146
+ * The PostScript Language Reference Manual specifications
147
+ * The actual output of ps2svg-ts (due to its bug)
148
+
149
+ Old fixtures expecting incorrect ordering have been renamed with `.incorrect` suffix for reference.