postsvg 0.2.5 → 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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +49 -533
  3. data/CLAUDE.md +3 -3
  4. data/Gemfile +2 -2
  5. data/lib/postsvg/cli.rb +5 -10
  6. data/lib/postsvg/color.rb +132 -0
  7. data/lib/postsvg/format_number.rb +22 -0
  8. data/lib/postsvg/graphics_context.rb +1 -2
  9. data/lib/postsvg/matrix.rb +106 -0
  10. data/lib/postsvg/model/literals/array.rb +41 -0
  11. data/lib/postsvg/model/literals/dictionary.rb +34 -0
  12. data/lib/postsvg/model/literals/hex.rb +37 -0
  13. data/lib/postsvg/model/literals/name.rb +40 -0
  14. data/lib/postsvg/model/literals/number.rb +36 -0
  15. data/lib/postsvg/model/literals/procedure.rb +41 -0
  16. data/lib/postsvg/model/literals/string.rb +30 -0
  17. data/lib/postsvg/model/literals.rb +19 -0
  18. data/lib/postsvg/model/operator.rb +58 -0
  19. data/lib/postsvg/model/operators/arithmetic.rb +264 -0
  20. data/lib/postsvg/model/operators/boolean.rb +182 -0
  21. data/lib/postsvg/model/operators/color.rb +74 -0
  22. data/lib/postsvg/model/operators/container.rb +186 -0
  23. data/lib/postsvg/model/operators/control_flow.rb +119 -0
  24. data/lib/postsvg/model/operators/device.rb +21 -0
  25. data/lib/postsvg/model/operators/dictionary.rb +118 -0
  26. data/lib/postsvg/model/operators/font.rb +121 -0
  27. data/lib/postsvg/model/operators/graphics_state.rb +84 -0
  28. data/lib/postsvg/model/operators/painting.rb +29 -0
  29. data/lib/postsvg/model/operators/path.rb +169 -0
  30. data/lib/postsvg/model/operators/stack.rb +72 -0
  31. data/lib/postsvg/model/operators/transformations.rb +103 -0
  32. data/lib/postsvg/model/operators.rb +89 -0
  33. data/lib/postsvg/model/program.rb +68 -0
  34. data/lib/postsvg/model/token.rb +43 -0
  35. data/lib/postsvg/model.rb +17 -0
  36. data/lib/postsvg/options.rb +1 -1
  37. data/lib/postsvg/parser/postscript_parser.rb +7 -7
  38. data/lib/postsvg/renderer.rb +3 -7
  39. data/lib/postsvg/serializer.rb +325 -0
  40. data/lib/postsvg/source/ast_builder.rb +308 -0
  41. data/lib/postsvg/source/lexer.rb +322 -0
  42. data/lib/postsvg/source/operand_stack.rb +55 -0
  43. data/lib/postsvg/source.rb +21 -0
  44. data/lib/postsvg/svg/clip_path_registry.rb +1 -3
  45. data/lib/postsvg/svg/element.rb +2 -4
  46. data/lib/postsvg/svg/elements/ellipse.rb +1 -2
  47. data/lib/postsvg/svg/elements/svg.rb +1 -2
  48. data/lib/postsvg/svg/paint.rb +1 -1
  49. data/lib/postsvg/svg/parser.rb +3 -3
  50. data/lib/postsvg/svg/path_data/parser.rb +6 -10
  51. data/lib/postsvg/svg/stroke.rb +2 -4
  52. data/lib/postsvg/svg/transform_list.rb +1 -1
  53. data/lib/postsvg/svg.rb +1 -1
  54. data/lib/postsvg/svg_builder.rb +11 -15
  55. data/lib/postsvg/translation/arc_converter.rb +12 -22
  56. data/lib/postsvg/translation/bounding_box.rb +4 -7
  57. data/lib/postsvg/translation/handlers/circle_handler.rb +2 -2
  58. data/lib/postsvg/translation/handlers/ellipse_handler.rb +3 -8
  59. data/lib/postsvg/translation/handlers/group_handler.rb +1 -2
  60. data/lib/postsvg/translation/handlers/image_handler.rb +2 -3
  61. data/lib/postsvg/translation/handlers/line_handler.rb +2 -6
  62. data/lib/postsvg/translation/handlers/open_handler.rb +2 -3
  63. data/lib/postsvg/translation/handlers/path_handler.rb +41 -57
  64. data/lib/postsvg/translation/handlers/polygon_handler.rb +2 -4
  65. data/lib/postsvg/translation/handlers/polyline_handler.rb +2 -4
  66. data/lib/postsvg/translation/handlers/rect_handler.rb +5 -10
  67. data/lib/postsvg/translation/handlers/shared.rb +11 -11
  68. data/lib/postsvg/translation/handlers/svg_handler.rb +2 -3
  69. data/lib/postsvg/translation/handlers/text_handler.rb +7 -8
  70. data/lib/postsvg/translation/handlers.rb +1 -2
  71. data/lib/postsvg/translation/ps_renderer.rb +1 -2
  72. data/lib/postsvg/translation/record_emitter.rb +0 -11
  73. data/lib/postsvg/version.rb +1 -1
  74. data/lib/postsvg/visitors/ps_visitor/arithmetic.rb +6 -6
  75. data/lib/postsvg/visitors/ps_visitor/color.rb +5 -8
  76. data/lib/postsvg/visitors/ps_visitor/common.rb +2 -1
  77. data/lib/postsvg/visitors/ps_visitor/container.rb +13 -13
  78. data/lib/postsvg/visitors/ps_visitor/control_flow.rb +3 -3
  79. data/lib/postsvg/visitors/ps_visitor/dictionary.rb +4 -8
  80. data/lib/postsvg/visitors/ps_visitor/font.rb +4 -14
  81. data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +4 -4
  82. data/lib/postsvg/visitors/ps_visitor/painting.rb +1 -1
  83. data/lib/postsvg/visitors/ps_visitor/path.rb +4 -9
  84. data/lib/postsvg.rb +38 -43
  85. metadata +41 -21
@@ -44,25 +44,25 @@ module Postsvg
44
44
  @stack << value
45
45
  end
46
46
 
47
- def visit_put(_op, _ctx)
47
+ def visit_put(op, _ctx)
48
48
  # PS put mutates in place. Our value objects are immutable.
49
49
  # Marked as a comment in the output so the gap is visible.
50
50
  @builder.comment("put: in-place mutation not supported")
51
51
  end
52
52
 
53
53
  def visit_getinterval(op, _ctx)
54
- @stack << case op.operand
55
- when String
56
- op.operand[op.start, op.count]
57
- when Array
58
- op.operand[op.start, op.count]
59
- when Model::Literals::StringLiteral
60
- op.operand.value[op.start, op.count]
61
- when Model::Literals::ArrayLiteral
62
- op.operand.elements[op.start, op.count]
63
- else
64
- ""
65
- end
54
+ case op.operand
55
+ when String
56
+ @stack << op.operand[op.start, op.count]
57
+ when Array
58
+ @stack << op.operand[op.start, op.count]
59
+ when Model::Literals::StringLiteral
60
+ @stack << op.operand.value[op.start, op.count]
61
+ when Model::Literals::ArrayLiteral
62
+ @stack << op.operand.elements[op.start, op.count]
63
+ else
64
+ @stack << ""
65
+ end
66
66
  end
67
67
 
68
68
  def visit_putinterval(_op, _ctx)
@@ -39,7 +39,7 @@ module Postsvg
39
39
  body = @stack.pop
40
40
  LOOP_LIMIT.times do
41
41
  descend_into_procedure(body)
42
- rescue Postscript::ExitSignal
42
+ rescue Postsvg::ExitSignal
43
43
  break
44
44
  end
45
45
  end
@@ -74,11 +74,11 @@ module Postsvg
74
74
  end
75
75
 
76
76
  def visit_exit(_op, _ctx)
77
- raise Postscript::ExitSignal
77
+ raise Postsvg::ExitSignal
78
78
  end
79
79
 
80
80
  def visit_quit(_op, _ctx)
81
- raise Postscript::QuitSignal
81
+ raise Postsvg::QuitSignal
82
82
  end
83
83
 
84
84
  def visit_exec(_op, _ctx)
@@ -72,14 +72,10 @@ module Postsvg
72
72
 
73
73
  def visit_maxlength(op, _ctx)
74
74
  # PS dictionaries can grow dynamically; report current size.
75
- @stack << dict_size(op.operand)
76
- end
77
-
78
- def dict_size(operand)
79
- case operand
80
- when Hash then operand.length
81
- when Model::Literals::Dictionary then operand.entries.length
82
- else 0
75
+ case op.operand
76
+ when Hash then @stack << op.operand.length
77
+ when Model::Literals::Dictionary then @stack << op.operand.entries.length
78
+ else @stack << 0
83
79
  end
84
80
  end
85
81
 
@@ -31,25 +31,15 @@ module Postsvg
31
31
  end
32
32
 
33
33
  def visit_scalefont(op, _ctx)
34
- size = op.size.to_i
34
+ size = op.size.to_i || op.size.to_f
35
35
  font = @stack.pop
36
- base = if font.is_a?(FontRef)
37
- font
38
- else
39
- FontRef.new(name: "Helvetica",
40
- size: nil)
41
- end
36
+ base = font.is_a?(FontRef) ? font : FontRef.new(name: "Helvetica", size: nil)
42
37
  @stack << FontRef.new(name: base.name, size: size.to_f)
43
38
  end
44
39
 
45
40
  def visit_setfont(_op, _ctx)
46
41
  font = @stack.pop
47
- ref = if font.is_a?(FontRef)
48
- font
49
- else
50
- FontRef.new(name: "Helvetica",
51
- size: 12.0)
52
- end
42
+ ref = font.is_a?(FontRef) ? font : FontRef.new(name: "Helvetica", size: 12.0)
53
43
  @graphics.update(font_name: ref.name || "Helvetica",
54
44
  font_size: ref.size || 12.0)
55
45
  end
@@ -77,7 +67,7 @@ module Postsvg
77
67
  y: -screen_pos[:y],
78
68
  font_family: @graphics.current.font_name,
79
69
  font_size: @graphics.current.font_size,
80
- color: color || Postscript::Color::BLACK,
70
+ color: color || Postsvg::Color::BLACK,
81
71
  transform: "scale(1 -1)",
82
72
  )
83
73
  @path.reset
@@ -23,14 +23,12 @@ module Postsvg
23
23
  end
24
24
 
25
25
  def visit_setlinecap(op, _ctx)
26
- name = { 0 => :butt, 1 => :round, 2 => :square }.fetch(op.cap_code,
27
- :butt)
26
+ name = { 0 => :butt, 1 => :round, 2 => :square }.fetch(op.cap_code, :butt)
28
27
  @graphics.update(line_cap: name)
29
28
  end
30
29
 
31
30
  def visit_setlinejoin(op, _ctx)
32
- name = { 0 => :miter, 1 => :round, 2 => :bevel }.fetch(op.join_code,
33
- :miter)
31
+ name = { 0 => :miter, 1 => :round, 2 => :bevel }.fetch(op.join_code, :miter)
34
32
  @graphics.update(line_join: name)
35
33
  end
36
34
 
@@ -46,6 +44,8 @@ module Postsvg
46
44
  pattern.map { |v| FormatNumber.call(v.to_f) }.join(" ")
47
45
  when Numeric
48
46
  FormatNumber.call(pattern.to_f)
47
+ else
48
+ nil
49
49
  end
50
50
  @graphics.update(dash: dash_str)
51
51
  end
@@ -66,7 +66,7 @@ module Postsvg
66
66
  line_cap: ctx.line_cap,
67
67
  line_join: ctx.line_join,
68
68
  dash: ctx.dash,
69
- clip_path_id: clip_path_id
69
+ clip_path_id: clip_path_id,
70
70
  )
71
71
  end
72
72
 
@@ -23,8 +23,7 @@ module Postsvg
23
23
  pos = @graphics.current.last_text_position
24
24
  base_x = pos ? pos[:x] : 0
25
25
  base_y = pos ? pos[:y] : 0
26
- @graphics.update(last_text_position: { x: base_x + op.dx,
27
- y: base_y + op.dy })
26
+ @graphics.update(last_text_position: { x: base_x + op.dx, y: base_y + op.dy })
28
27
  end
29
28
 
30
29
  def visit_lineto(op, _ctx)
@@ -78,8 +77,8 @@ module Postsvg
78
77
 
79
78
  # If the current path is empty, the arc implicitly starts
80
79
  # with a moveto to its starting point.
81
- if (@path.empty? || last_command_starts_with?("M")) && @path.empty?
82
- @path.move_to(start_x, start_y)
80
+ if @path.empty? || last_command_starts_with?("M")
81
+ @path.move_to(start_x, start_y) if @path.empty?
83
82
  end
84
83
 
85
84
  delta = (angle2 - angle1) % 360
@@ -87,11 +86,7 @@ module Postsvg
87
86
  large_arc = delta > 180
88
87
 
89
88
  # SVG sweep flag is 1 when the arc is drawn clockwise.
90
- svg_sweep = if sweep
91
- delta.positive? ? 1 : 0
92
- else
93
- 0
94
- end
89
+ svg_sweep = sweep ? (delta.positive? ? 1 : 0) : 0
95
90
  if delta.abs < 1e-6
96
91
  # Full circle: emit two arcs.
97
92
  mid_angle = angle1 + 180
data/lib/postsvg.rb CHANGED
@@ -1,68 +1,63 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "postscript"
4
3
  require "parslet"
5
4
  require "thor"
6
- require "leptris"
5
+ require "nokogiri"
7
6
 
8
7
  module Postsvg
9
8
  autoload :VERSION, "postsvg/version"
10
9
 
11
- # ============================================================
12
- # Posts created and managed by the +postscript+ gem.
13
- # ============================================================
14
- #
15
- # `postscript` owns PS source parsing, the typed PS domain model,
16
- # and the PS source serializer. The `Postscript` namespace is the
17
- # canonical location; the aliases below keep existing user code
18
- # working during the transition (deprecated, will be removed at
19
- # postsvg 1.0).
20
- #
21
- # If you maintain postsvg, prefer `Postscript::*` in new code.
22
-
23
- Source = Postscript::Source
24
- Model = Postscript::Model
25
- Serializer = Postscript::Serializer
26
- Matrix = Postscript::Matrix
27
- Color = Postscript::Color
28
- FormatNumber = Postscript::FormatNumber
29
-
30
- ParseError = Postscript::ParseError
31
- LexError = Postscript::LexError
32
- SyntaxError = Postscript::SyntaxError
33
- StackUnderflowError = Postscript::StackUnderflowError
34
- UndefinedOperatorError = Postscript::UndefinedOperatorError
35
- RecursionLimitError = Postscript::RecursionLimitError
36
- RenderError = Postscript::RenderError
37
- SerializeError = Postscript::SerializeError
38
- ExitSignal = Postscript::ExitSignal
39
- QuitSignal = Postscript::QuitSignal
40
-
41
- # ============================================================
42
- # Errors that originate in the SVG side (kept here, not in postscript).
43
- # ============================================================
10
+ # Errors
44
11
  autoload :Error, "postsvg/errors"
12
+ autoload :ParseError, "postsvg/errors"
13
+ autoload :LexError, "postsvg/errors"
14
+ autoload :SyntaxError, "postsvg/errors"
15
+ autoload :RenderError, "postsvg/errors"
16
+ autoload :StackUnderflowError, "postsvg/errors"
17
+ autoload :UndefinedOperatorError, "postsvg/errors"
18
+ autoload :RecursionLimitError, "postsvg/errors"
19
+ autoload :SizeLimitError, "postsvg/errors"
45
20
  autoload :TranslationError, "postsvg/errors"
46
21
  autoload :UnsupportedElementError, "postsvg/errors"
47
22
  autoload :UnresolvedReferenceError, "postsvg/errors"
23
+ autoload :SerializeError, "postsvg/errors"
24
+ autoload :ConversionError, "postsvg/errors"
25
+ autoload :UnsupportedOperatorError, "postsvg/errors"
26
+ autoload :ExitSignal, "postsvg/errors"
27
+ autoload :QuitSignal, "postsvg/errors"
48
28
 
49
- # Legacy alias for RenderError (postsvg 0.1 API).
50
- ConversionError = RenderError
51
- # Legacy alias for UndefinedOperatorError (postsvg 0.1 API).
52
- UnsupportedOperatorError = UndefinedOperatorError
29
+ # Foundational value types
30
+ autoload :FormatNumber, "postsvg/format_number"
31
+ autoload :Matrix, "postsvg/matrix"
32
+ autoload :Color, "postsvg/color"
53
33
 
54
- # ============================================================
55
- # Rendering / SVG-side types owned by postsvg.
56
- # ============================================================
34
+ # Source-reading layer (PS source -> Model::Program)
35
+ autoload :Source, "postsvg/source"
36
+
37
+ # PS domain model
38
+ autoload :Model, "postsvg/model"
39
+
40
+ # Graphics state
57
41
  autoload :GraphicsContext, "postsvg/graphics_context"
58
42
  autoload :GraphicsStack, "postsvg/graphics_stack"
59
43
  autoload :PathBuilder, "postsvg/path_builder"
44
+
45
+ # SVG emission (forward direction)
60
46
  autoload :SvgBuilder, "postsvg/svg_builder"
47
+
48
+ # Forward direction (PS -> SVG)
61
49
  autoload :Options, "postsvg/options"
62
50
  autoload :Renderer, "postsvg/renderer"
63
51
  autoload :Visitors, "postsvg/visitors"
52
+
53
+ # SVG domain model (reverse direction input)
64
54
  autoload :Svg, "postsvg/svg"
55
+
56
+ # Reverse direction (SVG -> PS)
65
57
  autoload :Translation, "postsvg/translation"
58
+ autoload :Serializer, "postsvg/serializer"
59
+
60
+ # CLI
66
61
  autoload :CLI, "postsvg/cli"
67
62
 
68
63
  # Legacy implementations retained on disk for reference. Not on the
@@ -78,7 +73,7 @@ module Postsvg
78
73
  class << self
79
74
  # PS/EPS source -> SVG string.
80
75
  def to_svg(source, **options)
81
- program = Postscript::Source.parse(source)
76
+ program = Source.parse(source)
82
77
  Renderer.call(program, Options.new(**options))
83
78
  end
84
79
 
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postsvg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-16 00:00:00.000000000 Z
11
+ date: 2026-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: leptris
14
+ name: nokogiri
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.9.362
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.9.362
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: parslet
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: postscript
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '0.2'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '0.2'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: thor
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -188,19 +174,53 @@ files:
188
174
  - exe/postsvg
189
175
  - lib/postsvg.rb
190
176
  - lib/postsvg/cli.rb
177
+ - lib/postsvg/color.rb
191
178
  - lib/postsvg/colors.rb
192
179
  - lib/postsvg/converter.rb
193
180
  - lib/postsvg/errors.rb
181
+ - lib/postsvg/format_number.rb
194
182
  - lib/postsvg/graphics_context.rb
195
183
  - lib/postsvg/graphics_stack.rb
196
184
  - lib/postsvg/graphics_state.rb
197
185
  - lib/postsvg/interpreter.rb
186
+ - lib/postsvg/matrix.rb
187
+ - lib/postsvg/model.rb
188
+ - lib/postsvg/model/literals.rb
189
+ - lib/postsvg/model/literals/array.rb
190
+ - lib/postsvg/model/literals/dictionary.rb
191
+ - lib/postsvg/model/literals/hex.rb
192
+ - lib/postsvg/model/literals/name.rb
193
+ - lib/postsvg/model/literals/number.rb
194
+ - lib/postsvg/model/literals/procedure.rb
195
+ - lib/postsvg/model/literals/string.rb
196
+ - lib/postsvg/model/operator.rb
197
+ - lib/postsvg/model/operators.rb
198
+ - lib/postsvg/model/operators/arithmetic.rb
199
+ - lib/postsvg/model/operators/boolean.rb
200
+ - lib/postsvg/model/operators/color.rb
201
+ - lib/postsvg/model/operators/container.rb
202
+ - lib/postsvg/model/operators/control_flow.rb
203
+ - lib/postsvg/model/operators/device.rb
204
+ - lib/postsvg/model/operators/dictionary.rb
205
+ - lib/postsvg/model/operators/font.rb
206
+ - lib/postsvg/model/operators/graphics_state.rb
207
+ - lib/postsvg/model/operators/painting.rb
208
+ - lib/postsvg/model/operators/path.rb
209
+ - lib/postsvg/model/operators/stack.rb
210
+ - lib/postsvg/model/operators/transformations.rb
211
+ - lib/postsvg/model/program.rb
212
+ - lib/postsvg/model/token.rb
198
213
  - lib/postsvg/options.rb
199
214
  - lib/postsvg/parser.rb
200
215
  - lib/postsvg/parser/postscript_parser.rb
201
216
  - lib/postsvg/parser/transform.rb
202
217
  - lib/postsvg/path_builder.rb
203
218
  - lib/postsvg/renderer.rb
219
+ - lib/postsvg/serializer.rb
220
+ - lib/postsvg/source.rb
221
+ - lib/postsvg/source/ast_builder.rb
222
+ - lib/postsvg/source/lexer.rb
223
+ - lib/postsvg/source/operand_stack.rb
204
224
  - lib/postsvg/svg.rb
205
225
  - lib/postsvg/svg/attribute_parser.rb
206
226
  - lib/postsvg/svg/clip_path_registry.rb