postsvg 0.2.2 → 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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +49 -533
  3. data/Gemfile +1 -1
  4. data/lib/postsvg/cli.rb +5 -10
  5. data/lib/postsvg/color.rb +132 -0
  6. data/lib/postsvg/format_number.rb +22 -0
  7. data/lib/postsvg/graphics_context.rb +1 -2
  8. data/lib/postsvg/matrix.rb +106 -0
  9. data/lib/postsvg/model/literals/array.rb +41 -0
  10. data/lib/postsvg/model/literals/dictionary.rb +34 -0
  11. data/lib/postsvg/model/literals/hex.rb +37 -0
  12. data/lib/postsvg/model/literals/name.rb +40 -0
  13. data/lib/postsvg/model/literals/number.rb +36 -0
  14. data/lib/postsvg/model/literals/procedure.rb +41 -0
  15. data/lib/postsvg/model/literals/string.rb +30 -0
  16. data/lib/postsvg/model/literals.rb +19 -0
  17. data/lib/postsvg/model/operator.rb +58 -0
  18. data/lib/postsvg/model/operators/arithmetic.rb +264 -0
  19. data/lib/postsvg/model/operators/boolean.rb +182 -0
  20. data/lib/postsvg/model/operators/color.rb +74 -0
  21. data/lib/postsvg/model/operators/container.rb +186 -0
  22. data/lib/postsvg/model/operators/control_flow.rb +119 -0
  23. data/lib/postsvg/model/operators/device.rb +21 -0
  24. data/lib/postsvg/model/operators/dictionary.rb +118 -0
  25. data/lib/postsvg/model/operators/font.rb +121 -0
  26. data/lib/postsvg/model/operators/graphics_state.rb +84 -0
  27. data/lib/postsvg/model/operators/painting.rb +29 -0
  28. data/lib/postsvg/model/operators/path.rb +169 -0
  29. data/lib/postsvg/model/operators/stack.rb +72 -0
  30. data/lib/postsvg/model/operators/transformations.rb +103 -0
  31. data/lib/postsvg/model/operators.rb +89 -0
  32. data/lib/postsvg/model/program.rb +68 -0
  33. data/lib/postsvg/model/token.rb +43 -0
  34. data/lib/postsvg/model.rb +17 -0
  35. data/lib/postsvg/options.rb +1 -1
  36. data/lib/postsvg/parser/postscript_parser.rb +7 -7
  37. data/lib/postsvg/renderer.rb +3 -7
  38. data/lib/postsvg/serializer.rb +325 -0
  39. data/lib/postsvg/source/ast_builder.rb +308 -0
  40. data/lib/postsvg/source/lexer.rb +322 -0
  41. data/lib/postsvg/source/operand_stack.rb +55 -0
  42. data/lib/postsvg/source.rb +21 -0
  43. data/lib/postsvg/svg/clip_path_registry.rb +1 -3
  44. data/lib/postsvg/svg/element.rb +1 -3
  45. data/lib/postsvg/svg/elements/ellipse.rb +1 -2
  46. data/lib/postsvg/svg/elements/svg.rb +1 -2
  47. data/lib/postsvg/svg/paint.rb +1 -1
  48. data/lib/postsvg/svg/path_data/parser.rb +6 -10
  49. data/lib/postsvg/svg/stroke.rb +2 -4
  50. data/lib/postsvg/svg/transform_list.rb +1 -1
  51. data/lib/postsvg/svg_builder.rb +11 -15
  52. data/lib/postsvg/translation/arc_converter.rb +12 -22
  53. data/lib/postsvg/translation/bounding_box.rb +4 -7
  54. data/lib/postsvg/translation/handlers/circle_handler.rb +2 -2
  55. data/lib/postsvg/translation/handlers/ellipse_handler.rb +3 -8
  56. data/lib/postsvg/translation/handlers/group_handler.rb +1 -2
  57. data/lib/postsvg/translation/handlers/image_handler.rb +2 -3
  58. data/lib/postsvg/translation/handlers/line_handler.rb +2 -6
  59. data/lib/postsvg/translation/handlers/open_handler.rb +2 -3
  60. data/lib/postsvg/translation/handlers/path_handler.rb +41 -57
  61. data/lib/postsvg/translation/handlers/polygon_handler.rb +2 -4
  62. data/lib/postsvg/translation/handlers/polyline_handler.rb +2 -4
  63. data/lib/postsvg/translation/handlers/rect_handler.rb +5 -10
  64. data/lib/postsvg/translation/handlers/shared.rb +11 -11
  65. data/lib/postsvg/translation/handlers/svg_handler.rb +2 -3
  66. data/lib/postsvg/translation/handlers/text_handler.rb +7 -8
  67. data/lib/postsvg/translation/handlers.rb +1 -2
  68. data/lib/postsvg/translation/ps_renderer.rb +1 -2
  69. data/lib/postsvg/version.rb +1 -1
  70. data/lib/postsvg/visitors/ps_visitor/arithmetic.rb +6 -6
  71. data/lib/postsvg/visitors/ps_visitor/color.rb +5 -8
  72. data/lib/postsvg/visitors/ps_visitor/common.rb +2 -1
  73. data/lib/postsvg/visitors/ps_visitor/container.rb +13 -13
  74. data/lib/postsvg/visitors/ps_visitor/control_flow.rb +3 -3
  75. data/lib/postsvg/visitors/ps_visitor/dictionary.rb +4 -8
  76. data/lib/postsvg/visitors/ps_visitor/font.rb +4 -14
  77. data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +4 -4
  78. data/lib/postsvg/visitors/ps_visitor/painting.rb +1 -1
  79. data/lib/postsvg/visitors/ps_visitor/path.rb +4 -9
  80. data/lib/postsvg.rb +37 -42
  81. metadata +36 -16
@@ -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
@@ -22,7 +22,7 @@ module Postsvg
22
22
  {
23
23
  eps: @eps, width: @width, height: @height,
24
24
  viewbox_override: @viewbox_override, verbose: @verbose,
25
- page_size: @page_size
25
+ page_size: @page_size,
26
26
  }
27
27
  end
28
28
  end
@@ -38,7 +38,7 @@ module Postsvg
38
38
  (str("\\") >> any) | (str(")").absent? >> any)
39
39
  end
40
40
  rule(:string) do
41
- str("(") >> string_char.repeat.as(:string) >> str(")")
41
+ (str("(") >> string_char.repeat.as(:string) >> str(")"))
42
42
  end
43
43
 
44
44
  # Operators and keywords
@@ -49,16 +49,16 @@ module Postsvg
49
49
 
50
50
  # Arrays
51
51
  rule(:array) do
52
- str("[") >> whitespace? >>
53
- value.repeat.as(:array) >>
54
- whitespace? >> str("]")
52
+ (str("[") >> whitespace? >>
53
+ value.repeat.as(:array) >>
54
+ whitespace? >> str("]"))
55
55
  end
56
56
 
57
57
  # Procedures (code blocks in curly braces)
58
58
  rule(:procedure) do
59
- str("{") >> whitespace? >>
60
- statement.repeat.as(:procedure) >>
61
- whitespace? >> str("}")
59
+ (str("{") >> whitespace? >>
60
+ statement.repeat.as(:procedure) >>
61
+ whitespace? >> str("}"))
62
62
  end
63
63
 
64
64
  # Values
@@ -13,10 +13,7 @@ module Postsvg
13
13
  attr_reader :program, :options, :builder, :visitor
14
14
 
15
15
  def initialize(program, options)
16
- unless program.is_a?(Model::Program)
17
- raise ArgumentError,
18
- "program must be a Model::Program"
19
- end
16
+ raise ArgumentError, "program must be a Model::Program" unless program.is_a?(Model::Program)
20
17
 
21
18
  @program = program
22
19
  @options = options
@@ -28,7 +25,7 @@ module Postsvg
28
25
  open_svg
29
26
  wrap_y_flip do
30
27
  visitor.visit_program(program)
31
- rescue Postscript::QuitSignal
28
+ rescue Postsvg::QuitSignal
32
29
  # PS +quit+ ends program execution; suppress and finalize SVG.
33
30
  end
34
31
  builder.close_svg
@@ -65,8 +62,7 @@ module Postsvg
65
62
  [viewbox, width, height]
66
63
  else
67
64
  width, height = options.page_size
68
- ["0 0 #{FormatNumber.call(width)} #{FormatNumber.call(height)}", width,
69
- height]
65
+ ["0 0 #{FormatNumber.call(width)} #{FormatNumber.call(height)}", width, height]
70
66
  end
71
67
  end
72
68