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,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ module Operators
6
+ module GraphicsState
7
+ class Gsave < Operator
8
+ register_as "gsave"
9
+ end
10
+
11
+ class Grestore < Operator
12
+ register_as "grestore"
13
+ end
14
+
15
+ class Grestoreall < Operator
16
+ register_as "grestoreall"
17
+ end
18
+
19
+ class Setlinewidth < Operator
20
+ register_as "setlinewidth"
21
+ attr_reader :width
22
+ def initialize(width:)
23
+ @width = width
24
+ freeze
25
+ end
26
+ def self.from_operands(stack)
27
+ new(width: stack.pop_number)
28
+ end
29
+ end
30
+
31
+ class Setlinecap < Operator
32
+ register_as "setlinecap"
33
+ attr_reader :cap_code
34
+ def initialize(cap_code:)
35
+ @cap_code = cap_code.to_i
36
+ freeze
37
+ end
38
+ def self.from_operands(stack)
39
+ new(cap_code: stack.pop_number.to_i)
40
+ end
41
+ end
42
+
43
+ class Setlinejoin < Operator
44
+ register_as "setlinejoin"
45
+ attr_reader :join_code
46
+ def initialize(join_code:)
47
+ @join_code = join_code.to_i
48
+ freeze
49
+ end
50
+ def self.from_operands(stack)
51
+ new(join_code: stack.pop_number.to_i)
52
+ end
53
+ end
54
+
55
+ class Setmiterlimit < Operator
56
+ register_as "setmiterlimit"
57
+ attr_reader :limit
58
+ def initialize(limit:)
59
+ @limit = limit
60
+ freeze
61
+ end
62
+ def self.from_operands(stack)
63
+ new(limit: stack.pop_number)
64
+ end
65
+ end
66
+
67
+ class Setdash < Operator
68
+ register_as "setdash"
69
+ attr_reader :pattern, :offset
70
+ def initialize(pattern:, offset:)
71
+ @pattern = pattern
72
+ @offset = offset
73
+ freeze
74
+ end
75
+ def self.from_operands(stack)
76
+ offset = stack.pop_number
77
+ pattern = stack.pop
78
+ new(pattern: pattern, offset: offset)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ module Operators
6
+ module Painting
7
+ class Stroke < Operator
8
+ register_as "stroke", consumes: 0, produces: 0
9
+ end
10
+
11
+ class Fill < Operator
12
+ register_as "fill", consumes: 0, produces: 0
13
+ end
14
+
15
+ class Eofill < Operator
16
+ register_as "eofill", consumes: 0, produces: 0
17
+ end
18
+
19
+ class Clip < Operator
20
+ register_as "clip", consumes: 0, produces: 0
21
+ end
22
+
23
+ class Eoclip < Operator
24
+ register_as "eoclip", consumes: 0, produces: 0
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ module Operators
6
+ module Path
7
+ class Newpath < Operator
8
+ register_as "newpath"
9
+ end
10
+
11
+ class Moveto < Operator
12
+ register_as "moveto"
13
+ attr_reader :x, :y
14
+ def initialize(x:, y:)
15
+ @x = x
16
+ @y = y
17
+ freeze
18
+ end
19
+ def self.from_operands(stack)
20
+ y = stack.pop_number
21
+ x = stack.pop_number
22
+ new(x: x, y: y)
23
+ end
24
+ end
25
+
26
+ class Rmoveto < Operator
27
+ register_as "rmoveto"
28
+ attr_reader :dx, :dy
29
+ def initialize(dx:, dy:)
30
+ @dx = dx
31
+ @dy = dy
32
+ freeze
33
+ end
34
+ def self.from_operands(stack)
35
+ dy = stack.pop_number
36
+ dx = stack.pop_number
37
+ new(dx: dx, dy: dy)
38
+ end
39
+ end
40
+
41
+ class Lineto < Operator
42
+ register_as "lineto"
43
+ attr_reader :x, :y
44
+ def initialize(x:, y:)
45
+ @x = x
46
+ @y = y
47
+ freeze
48
+ end
49
+ def self.from_operands(stack)
50
+ y = stack.pop_number
51
+ x = stack.pop_number
52
+ new(x: x, y: y)
53
+ end
54
+ end
55
+
56
+ class Rlineto < Operator
57
+ register_as "rlineto"
58
+ attr_reader :dx, :dy
59
+ def initialize(dx:, dy:)
60
+ @dx = dx
61
+ @dy = dy
62
+ freeze
63
+ end
64
+ def self.from_operands(stack)
65
+ dy = stack.pop_number
66
+ dx = stack.pop_number
67
+ new(dx: dx, dy: dy)
68
+ end
69
+ end
70
+
71
+ class Curveto < Operator
72
+ register_as "curveto"
73
+ attr_reader :x1, :y1, :x2, :y2, :x3, :y3
74
+ def initialize(x1:, y1:, x2:, y2:, x3:, y3:)
75
+ @x1 = x1
76
+ @y1 = y1
77
+ @x2 = x2
78
+ @y2 = y2
79
+ @x3 = x3
80
+ @y3 = y3
81
+ freeze
82
+ end
83
+ def self.from_operands(stack)
84
+ y3 = stack.pop_number
85
+ x3 = stack.pop_number
86
+ y2 = stack.pop_number
87
+ x2 = stack.pop_number
88
+ y1 = stack.pop_number
89
+ x1 = stack.pop_number
90
+ new(x1: x1, y1: y1, x2: x2, y2: y2, x3: x3, y3: y3)
91
+ end
92
+ end
93
+
94
+ class Rcurveto < Operator
95
+ register_as "rcurveto"
96
+ attr_reader :dx1, :dy1, :dx2, :dy2, :dx3, :dy3
97
+ def initialize(dx1:, dy1:, dx2:, dy2:, dx3:, dy3:)
98
+ @dx1 = dx1
99
+ @dy1 = dy1
100
+ @dx2 = dx2
101
+ @dy2 = dy2
102
+ @dx3 = dx3
103
+ @dy3 = dy3
104
+ freeze
105
+ end
106
+ def self.from_operands(stack)
107
+ dy3 = stack.pop_number
108
+ dx3 = stack.pop_number
109
+ dy2 = stack.pop_number
110
+ dx2 = stack.pop_number
111
+ dy1 = stack.pop_number
112
+ dx1 = stack.pop_number
113
+ new(dx1: dx1, dy1: dy1, dx2: dx2, dy2: dy2, dx3: dx3, dy3: dy3)
114
+ end
115
+ end
116
+
117
+ class Arc < Operator
118
+ register_as "arc"
119
+ attr_reader :x, :y, :radius, :angle1, :angle2
120
+ def initialize(x:, y:, radius:, angle1:, angle2:)
121
+ @x = x
122
+ @y = y
123
+ @radius = radius
124
+ @angle1 = angle1
125
+ @angle2 = angle2
126
+ freeze
127
+ end
128
+ def self.from_operands(stack)
129
+ angle2 = stack.pop_number
130
+ angle1 = stack.pop_number
131
+ radius = stack.pop_number
132
+ y = stack.pop_number
133
+ x = stack.pop_number
134
+ new(x: x, y: y, radius: radius, angle1: angle1, angle2: angle2)
135
+ end
136
+ end
137
+
138
+ class Arcn < Operator
139
+ register_as "arcn"
140
+ attr_reader :x, :y, :radius, :angle1, :angle2
141
+ def initialize(x:, y:, radius:, angle1:, angle2:)
142
+ @x = x
143
+ @y = y
144
+ @radius = radius
145
+ @angle1 = angle1
146
+ @angle2 = angle2
147
+ freeze
148
+ end
149
+ def self.from_operands(stack)
150
+ angle2 = stack.pop_number
151
+ angle1 = stack.pop_number
152
+ radius = stack.pop_number
153
+ y = stack.pop_number
154
+ x = stack.pop_number
155
+ new(x: x, y: y, radius: radius, angle1: angle1, angle2: angle2)
156
+ end
157
+ end
158
+
159
+ class Closepath < Operator
160
+ register_as "closepath"
161
+ end
162
+
163
+ class Currentpoint < Operator
164
+ register_as "currentpoint"
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ module Operators
6
+ module Stack
7
+ class Pop < Operator
8
+ register_as "pop", consumes: 1, produces: 0
9
+
10
+ def self.from_operands(stack)
11
+ stack.pop
12
+ new
13
+ end
14
+ end
15
+
16
+ class Exch < Operator
17
+ register_as "exch", consumes: 2, produces: 2
18
+
19
+ def self.from_operands(stack)
20
+ stack.pop
21
+ stack.pop
22
+ new
23
+ end
24
+ end
25
+
26
+ class Dup < Operator
27
+ register_as "dup", consumes: 1, produces: 2
28
+
29
+ def self.from_operands(stack)
30
+ stack.pop
31
+ new
32
+ end
33
+ end
34
+
35
+ class Index < Operator
36
+ register_as "index", consumes: 1, produces: 1
37
+ attr_reader :index
38
+ def initialize(index:)
39
+ @index = index
40
+ freeze
41
+ end
42
+ def self.from_operands(stack)
43
+ new(index: stack.pop_number.to_i)
44
+ end
45
+ end
46
+
47
+ class Roll < Operator
48
+ register_as "roll", consumes: 2, produces: 0
49
+ attr_reader :count, :positions
50
+ def initialize(count:, positions:)
51
+ @count = count
52
+ @positions = positions
53
+ freeze
54
+ end
55
+ def self.from_operands(stack)
56
+ positions = stack.pop_number.to_i
57
+ count = stack.pop_number.to_i
58
+ new(count: count, positions: positions)
59
+ end
60
+ end
61
+
62
+ class Clear < Operator
63
+ register_as "clear", consumes: 0, produces: 0
64
+ end
65
+
66
+ class Count < Operator
67
+ register_as "count", consumes: 0, produces: 1
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ module Operators
6
+ module Transformations
7
+ class Translate < Operator
8
+ register_as "translate"
9
+ attr_reader :tx, :ty
10
+ def initialize(tx:, ty:)
11
+ @tx = tx
12
+ @ty = ty
13
+ freeze
14
+ end
15
+ def self.from_operands(stack)
16
+ ty = stack.pop_number
17
+ tx = stack.pop_number
18
+ new(tx: tx, ty: ty)
19
+ end
20
+ end
21
+
22
+ class Scale < Operator
23
+ register_as "scale"
24
+ attr_reader :sx, :sy
25
+ def initialize(sx:, sy:)
26
+ @sx = sx
27
+ @sy = sy
28
+ freeze
29
+ end
30
+ def self.from_operands(stack)
31
+ sy = stack.pop_number
32
+ sx = stack.pop_number
33
+ new(sx: sx, sy: sy)
34
+ end
35
+ end
36
+
37
+ class Rotate < Operator
38
+ register_as "rotate"
39
+ attr_reader :angle
40
+ def initialize(angle:)
41
+ @angle = angle
42
+ freeze
43
+ end
44
+ def self.from_operands(stack)
45
+ new(angle: stack.pop_number)
46
+ end
47
+ end
48
+
49
+ class Concat < Operator
50
+ register_as "concat"
51
+ attr_reader :matrix
52
+ def initialize(matrix:)
53
+ @matrix = matrix
54
+ freeze
55
+ end
56
+ def self.from_operands(stack)
57
+ new(matrix: stack.pop)
58
+ end
59
+ end
60
+
61
+ class Matrix < Operator
62
+ register_as "matrix"
63
+ end
64
+
65
+ class Currentmatrix < Operator
66
+ register_as "currentmatrix"
67
+ end
68
+
69
+ class Setmatrix < Operator
70
+ register_as "setmatrix"
71
+ attr_reader :matrix
72
+ def initialize(matrix:)
73
+ @matrix = matrix
74
+ freeze
75
+ end
76
+ def self.from_operands(stack)
77
+ new(matrix: stack.pop)
78
+ end
79
+ end
80
+
81
+ class Transform < Operator
82
+ register_as "transform"
83
+ def self.from_operands(stack); end
84
+ end
85
+
86
+ class Dtransform < Operator
87
+ register_as "dtransform"
88
+ def self.from_operands(stack); end
89
+ end
90
+
91
+ class Itransform < Operator
92
+ register_as "itransform"
93
+ def self.from_operands(stack); end
94
+ end
95
+
96
+ class Idtransform < Operator
97
+ register_as "idtransform"
98
+ def self.from_operands(stack); end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ # PS operator catalogue. Each subclass of Model::Operator registers
6
+ # itself under its keyword. The parser looks keywords up here to
7
+ # decide which class to instantiate.
8
+ #
9
+ # Categories (mirrors PLRM chapter structure):
10
+ # - Stack, Arithmetic, Boolean, Path, Painting, Color,
11
+ # GraphicsState, Transformations, Dictionary, ControlFlow, Device
12
+ module Operators
13
+ autoload :Stack, "postsvg/model/operators/stack"
14
+ autoload :Arithmetic, "postsvg/model/operators/arithmetic"
15
+ autoload :Boolean, "postsvg/model/operators/boolean"
16
+ autoload :Path, "postsvg/model/operators/path"
17
+ autoload :Painting, "postsvg/model/operators/painting"
18
+ autoload :Color, "postsvg/model/operators/color"
19
+ autoload :GraphicsState, "postsvg/model/operators/graphics_state"
20
+ autoload :Transformations, "postsvg/model/operators/transformations"
21
+ autoload :Dictionary, "postsvg/model/operators/dictionary"
22
+ autoload :ControlFlow, "postsvg/model/operators/control_flow"
23
+ autoload :Device, "postsvg/model/operators/device"
24
+ autoload :Font, "postsvg/model/operators/font"
25
+ autoload :Container, "postsvg/model/operators/container"
26
+
27
+ @registry = {}
28
+ @visit_names = {}
29
+
30
+ class << self
31
+ # @api private Used by Operator subclasses at load time.
32
+ def register(keyword, klass)
33
+ @registry[keyword] = klass
34
+ end
35
+
36
+ # Lookup by PS keyword. Returns nil for unknown keywords.
37
+ def [](keyword)
38
+ @registry[keyword]
39
+ end
40
+
41
+ def registered?(keyword)
42
+ @registry.key?(keyword)
43
+ end
44
+
45
+ def keywords
46
+ @registry.keys
47
+ end
48
+
49
+ # Force-load every operator category so the registry is fully
50
+ # populated. Call this once before relying on [+].
51
+ def load_all!
52
+ constants.each { |c| const_get(c) }
53
+ end
54
+ end
55
+ end
56
+
57
+ # Wrapper for operator tokens with no registered class. Carries
58
+ # the original keyword so the visitor can emit a comment / warn.
59
+ class UnknownOperator < Operator
60
+ attr_reader :keyword
61
+
62
+ def initialize(keyword:)
63
+ @keyword = keyword
64
+ freeze
65
+ end
66
+
67
+ def visit_name = "unknown"
68
+
69
+ def self.from_operands(_stack); end
70
+ end
71
+
72
+ # A bare name reference resolved to a user-defined procedure via
73
+ # +def+. The renderer descends into +procedure+ with the current
74
+ # context, mirroring how PostScript executes the procedure body.
75
+ class InvokeProcedure < Operator
76
+ attr_reader :name, :procedure
77
+
78
+ def initialize(name:, procedure:)
79
+ @name = name
80
+ @procedure = procedure
81
+ freeze
82
+ end
83
+
84
+ def visit_name = "invoke_procedure"
85
+
86
+ def keyword = name.to_s
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ # Top-level program node. Carries DSC header (BoundingBox, Title,
6
+ # etc.) and the body — an ordered list of statements, each either a
7
+ # literal (Number/Name/String/Array/Procedure/Dictionary) or an
8
+ # Operator instance.
9
+ class Program
10
+ attr_reader :header, :body
11
+
12
+ def initialize(header: Header.new, body: [])
13
+ @header = header
14
+ @body = body
15
+ freeze
16
+ end
17
+
18
+ def with(header: nil, body: nil)
19
+ Program.new(header: header || @header, body: body || @body)
20
+ end
21
+
22
+ def append(statement)
23
+ Program.new(header: @header, body: @body + [statement])
24
+ end
25
+
26
+ def concat(statements)
27
+ Program.new(header: @header, body: @body + statements)
28
+ end
29
+
30
+ # DSC header: structured document structuring comments.
31
+ class Header
32
+ attr_reader :bounding_box, :hires_bounding_box, :title, :creator,
33
+ :creation_date, :pages, :page_count, :epsf,
34
+ :language_level, :custom
35
+
36
+ def initialize(bounding_box: nil, hires_bounding_box: nil, title: nil,
37
+ creator: nil, creation_date: nil, pages: nil,
38
+ page_count: nil, epsf: false, language_level: nil,
39
+ custom: {})
40
+ @bounding_box = bounding_box
41
+ @hires_bounding_box = hires_bounding_box
42
+ @title = title
43
+ @creator = creator
44
+ @creation_date = creation_date
45
+ @pages = pages
46
+ @page_count = page_count
47
+ @epsf = epsf
48
+ @language_level = language_level
49
+ @custom = custom.dup.freeze
50
+ freeze
51
+ end
52
+
53
+ def with(**overrides)
54
+ Header.new(**to_h.merge(overrides))
55
+ end
56
+
57
+ def to_h
58
+ {
59
+ bounding_box: bounding_box, hires_bounding_box: hires_bounding_box,
60
+ title: title, creator: creator, creation_date: creation_date,
61
+ pages: pages, page_count: page_count, epsf: epsf,
62
+ language_level: language_level, custom: custom,
63
+ }
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ module Model
5
+ # Lexical token. Type is one of:
6
+ # +:number+, +:operator+, +:name+, +:string+, +:hexstring+,
7
+ # +:proc_open+, +:proc_close+, +:array_open+, +:array_close+,
8
+ # +:dict_open+, +:dict_close+, +:dsc+.
9
+ #
10
+ # +literal+ is true when a +:name+ was written with a leading slash
11
+ # (i.e. it is a literal name, not an operator-style name reference).
12
+ class Token
13
+ attr_reader :type, :value, :line, :column, :literal
14
+
15
+ def initialize(type, value, line: nil, column: nil, literal: false)
16
+ @type = type
17
+ @value = value
18
+ @line = line
19
+ @column = column
20
+ @literal = literal
21
+ freeze
22
+ end
23
+
24
+ def position
25
+ [line, column]
26
+ end
27
+
28
+ def ==(other)
29
+ other.is_a?(Token) &&
30
+ other.type == @type &&
31
+ other.value == @value &&
32
+ other.line == @line &&
33
+ other.column == @column &&
34
+ other.literal == @literal
35
+ end
36
+ alias eql? ==
37
+
38
+ def hash
39
+ [@type, @value, @line, @column, @literal].hash
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ # PS domain model. Every value object that participates in the AST
5
+ # or in round-trip is a Model::* class. The lexer emits Model::Token;
6
+ # the parser emits Model::Program; the renderer / serializer consume
7
+ # Model::Program.
8
+ module Model
9
+ autoload :Token, "postsvg/model/token"
10
+ autoload :Program, "postsvg/model/program"
11
+ autoload :Literals, "postsvg/model/literals"
12
+ autoload :Operator, "postsvg/model/operator"
13
+ autoload :UnknownOperator, "postsvg/model/operator"
14
+ autoload :InvokeProcedure, "postsvg/model/operator"
15
+ autoload :Operators, "postsvg/model/operators"
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postsvg
4
+ # Shared options for both directions. Frozen on construction.
5
+ class Options
6
+ attr_reader :eps, :width, :height, :viewbox_override, :verbose, :page_size
7
+
8
+ DEFAULT_PAGE_SIZE = [595, 842].freeze # A4 in PostScript points
9
+
10
+ def initialize(eps: false, width: nil, height: nil, viewbox_override: nil,
11
+ verbose: false, page_size: nil)
12
+ @eps = eps
13
+ @width = width
14
+ @height = height
15
+ @viewbox_override = viewbox_override
16
+ @verbose = verbose
17
+ @page_size = page_size || DEFAULT_PAGE_SIZE
18
+ freeze
19
+ end
20
+
21
+ def to_h
22
+ {
23
+ eps: @eps, width: @width, height: @height,
24
+ viewbox_override: @viewbox_override, verbose: @verbose,
25
+ page_size: @page_size,
26
+ }
27
+ end
28
+ end
29
+ end