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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +105 -0
- data/CLAUDE.md +173 -0
- data/Gemfile +2 -3
- data/README.adoc +456 -179
- data/Rakefile +100 -0
- data/TODO.roadmap/00-architecture.md +139 -0
- data/TODO.roadmap/01-autoload-migration.md +39 -0
- data/TODO.roadmap/02-isolate-dormant-code.md +51 -0
- data/TODO.roadmap/03-domain-model.md +66 -0
- data/TODO.roadmap/04-lexer.md +40 -0
- data/TODO.roadmap/05-parser.md +43 -0
- data/TODO.roadmap/06-graphics-state.md +45 -0
- data/TODO.roadmap/07-matrix-and-color.md +37 -0
- data/TODO.roadmap/08-svg-builder.md +51 -0
- data/TODO.roadmap/09-renderer.md +52 -0
- data/TODO.roadmap/10-visitor.md +58 -0
- data/TODO.roadmap/11-operator-coverage.md +103 -0
- data/TODO.roadmap/12-svg-domain.md +62 -0
- data/TODO.roadmap/13-translation-handlers.md +69 -0
- data/TODO.roadmap/14-ps-serializer.md +49 -0
- data/TODO.roadmap/15-cli-and-public-api.md +47 -0
- data/TODO.roadmap/16-specs.md +84 -0
- data/TODO.roadmap/17-docs-sync.md +47 -0
- data/TODO.roadmap/18-performance-and-determinism.md +47 -0
- data/TODO.roadmap/19-error-model.md +45 -0
- data/TODO.roadmap/20-font-and-text.md +46 -0
- data/TODO.roadmap/21-images.md +45 -0
- data/TODO.roadmap/22-forms-and-resources.md +25 -0
- data/TODO.roadmap/23-level2-level3.md +52 -0
- data/TODO.roadmap/24-ci-and-release.md +42 -0
- data/TODO.roadmap/README.md +77 -0
- data/docs/.gitignore +29 -0
- data/docs/CHANGELOG.md +114 -0
- data/docs/COMPLETE_DOCUMENTATION_STATUS.md +376 -0
- data/docs/DEPLOYMENT.md +456 -0
- data/docs/DEPLOYMENT_INSTRUCTIONS.md +229 -0
- data/docs/DOCUMENTATION_PLAN.md +425 -0
- data/docs/FINAL_SUMMARY.md +657 -0
- data/docs/Gemfile +15 -0
- data/docs/README.md +327 -0
- data/docs/_config.yml +99 -0
- data/docs/advanced-topics.adoc +370 -0
- data/docs/api-reference/colors.adoc +705 -0
- data/docs/api-reference/converter.adoc +699 -0
- data/docs/api-reference/execution-context.adoc +1210 -0
- data/docs/api-reference/graphics-state.adoc +1070 -0
- data/docs/api-reference/interpreter.adoc +810 -0
- data/docs/api-reference/matrix.adoc +1179 -0
- data/docs/api-reference/path-builder.adoc +1284 -0
- data/docs/api-reference/postsvg-module.adoc +388 -0
- data/docs/api-reference/svg-generator.adoc +891 -0
- data/docs/api-reference/tokenizer.adoc +925 -0
- data/docs/api-reference.adoc +221 -0
- data/docs/architecture/command-registry.adoc +1191 -0
- data/docs/architecture/conversion-pipeline.adoc +746 -0
- data/docs/architecture/design-decisions.adoc +999 -0
- data/docs/architecture/generator-stage.adoc +1115 -0
- data/docs/architecture/graphics-state-model.adoc +1089 -0
- data/docs/architecture/interpreter-stage.adoc +1125 -0
- data/docs/architecture/parser-stage.adoc +1051 -0
- data/docs/architecture.adoc +354 -0
- data/docs/cli-reference/batch-command.adoc +616 -0
- data/docs/cli-reference/check-command.adoc +677 -0
- data/docs/cli-reference/cli-options.adoc +802 -0
- data/docs/cli-reference/convert-command.adoc +462 -0
- data/docs/cli-reference/version-command.adoc +296 -0
- data/docs/cli-reference.adoc +317 -0
- data/docs/concepts/conversion-pipeline.adoc +903 -0
- data/docs/concepts/coordinate-systems.adoc +836 -0
- data/docs/concepts/graphics-state.adoc +861 -0
- data/docs/concepts/path-operations.adoc +1076 -0
- data/docs/concepts/postscript-language.adoc +859 -0
- data/docs/concepts/svg-generation.adoc +937 -0
- data/docs/concepts.adoc +198 -0
- data/docs/contributing.adoc +443 -0
- data/docs/development.adoc +420 -0
- data/docs/faq.adoc +493 -0
- data/docs/getting-started/basic-usage.adoc +538 -0
- data/docs/getting-started/common-workflows.adoc +577 -0
- data/docs/getting-started/first-conversion.adoc +492 -0
- data/docs/getting-started/installation.adoc +534 -0
- data/docs/getting-started.adoc +94 -0
- data/docs/index.adoc +248 -0
- data/docs/optimization.adoc +196 -0
- data/docs/ps2svg_compatibility.adoc +149 -0
- data/docs/quick-reference.adoc +453 -0
- data/docs/sitemap.adoc +337 -0
- data/docs/troubleshooting.adoc +486 -0
- data/docs/validation.adoc +772 -0
- data/exe/postsvg +1 -0
- data/lib/postsvg/cli.rb +104 -57
- data/lib/postsvg/color.rb +132 -0
- data/lib/postsvg/errors.rb +68 -3
- data/lib/postsvg/format_number.rb +22 -0
- data/lib/postsvg/graphics_context.rb +80 -0
- data/lib/postsvg/graphics_stack.rb +43 -0
- data/lib/postsvg/model/literals/array.rb +41 -0
- data/lib/postsvg/model/literals/dictionary.rb +34 -0
- data/lib/postsvg/model/literals/hex.rb +37 -0
- data/lib/postsvg/model/literals/name.rb +40 -0
- data/lib/postsvg/model/literals/number.rb +36 -0
- data/lib/postsvg/model/literals/procedure.rb +41 -0
- data/lib/postsvg/model/literals/string.rb +30 -0
- data/lib/postsvg/model/literals.rb +19 -0
- data/lib/postsvg/model/operator.rb +58 -0
- data/lib/postsvg/model/operators/arithmetic.rb +264 -0
- data/lib/postsvg/model/operators/boolean.rb +182 -0
- data/lib/postsvg/model/operators/color.rb +74 -0
- data/lib/postsvg/model/operators/container.rb +186 -0
- data/lib/postsvg/model/operators/control_flow.rb +119 -0
- data/lib/postsvg/model/operators/device.rb +21 -0
- data/lib/postsvg/model/operators/dictionary.rb +118 -0
- data/lib/postsvg/model/operators/font.rb +121 -0
- data/lib/postsvg/model/operators/graphics_state.rb +84 -0
- data/lib/postsvg/model/operators/painting.rb +29 -0
- data/lib/postsvg/model/operators/path.rb +169 -0
- data/lib/postsvg/model/operators/stack.rb +72 -0
- data/lib/postsvg/model/operators/transformations.rb +103 -0
- data/lib/postsvg/model/operators.rb +89 -0
- data/lib/postsvg/model/program.rb +68 -0
- data/lib/postsvg/model/token.rb +43 -0
- data/lib/postsvg/model.rb +17 -0
- data/lib/postsvg/options.rb +29 -0
- data/lib/postsvg/renderer.rb +85 -0
- data/lib/postsvg/serializer.rb +325 -0
- data/lib/postsvg/source/ast_builder.rb +308 -0
- data/lib/postsvg/source/lexer.rb +322 -0
- data/lib/postsvg/source/operand_stack.rb +55 -0
- data/lib/postsvg/source.rb +21 -0
- data/lib/postsvg/svg/attribute_parser.rb +45 -0
- data/lib/postsvg/svg/clip_path_registry.rb +44 -0
- data/lib/postsvg/svg/document.rb +22 -0
- data/lib/postsvg/svg/element.rb +84 -0
- data/lib/postsvg/svg/elements/circle.rb +36 -0
- data/lib/postsvg/svg/elements/clip_path.rb +26 -0
- data/lib/postsvg/svg/elements/defs.rb +24 -0
- data/lib/postsvg/svg/elements/ellipse.rb +38 -0
- data/lib/postsvg/svg/elements/group.rb +37 -0
- data/lib/postsvg/svg/elements/image.rb +35 -0
- data/lib/postsvg/svg/elements/line.rb +36 -0
- data/lib/postsvg/svg/elements/path.rb +32 -0
- data/lib/postsvg/svg/elements/polygon.rb +12 -0
- data/lib/postsvg/svg/elements/polyline.rb +32 -0
- data/lib/postsvg/svg/elements/rect.rb +44 -0
- data/lib/postsvg/svg/elements/svg.rb +39 -0
- data/lib/postsvg/svg/elements/text.rb +42 -0
- data/lib/postsvg/svg/elements.rb +31 -0
- data/lib/postsvg/svg/paint.rb +34 -0
- data/lib/postsvg/svg/parser.rb +39 -0
- data/lib/postsvg/svg/path_data/command.rb +27 -0
- data/lib/postsvg/svg/path_data/parser.rb +82 -0
- data/lib/postsvg/svg/path_data.rb +17 -0
- data/lib/postsvg/svg/stroke.rb +31 -0
- data/lib/postsvg/svg/transform_list.rb +59 -0
- data/lib/postsvg/svg.rb +22 -0
- data/lib/postsvg/svg_builder.rb +249 -0
- data/lib/postsvg/translation/arc_converter.rb +86 -0
- data/lib/postsvg/translation/bounding_box.rb +59 -0
- data/lib/postsvg/translation/context.rb +34 -0
- data/lib/postsvg/translation/handler_registry.rb +38 -0
- data/lib/postsvg/translation/handlers/circle_handler.rb +28 -0
- data/lib/postsvg/translation/handlers/clip_path_handler.rb +13 -0
- data/lib/postsvg/translation/handlers/defs_handler.rb +15 -0
- data/lib/postsvg/translation/handlers/ellipse_handler.rb +31 -0
- data/lib/postsvg/translation/handlers/group_handler.rb +21 -0
- data/lib/postsvg/translation/handlers/image_handler.rb +20 -0
- data/lib/postsvg/translation/handlers/line_handler.rb +23 -0
- data/lib/postsvg/translation/handlers/open_handler.rb +18 -0
- data/lib/postsvg/translation/handlers/path_handler.rb +356 -0
- data/lib/postsvg/translation/handlers/polygon_handler.rb +33 -0
- data/lib/postsvg/translation/handlers/polyline_handler.rb +31 -0
- data/lib/postsvg/translation/handlers/rect_handler.rb +27 -0
- data/lib/postsvg/translation/handlers/shared.rb +110 -0
- data/lib/postsvg/translation/handlers/svg_handler.rb +25 -0
- data/lib/postsvg/translation/handlers/text_handler.rb +56 -0
- data/lib/postsvg/translation/handlers.rb +25 -0
- data/lib/postsvg/translation/ps_renderer.rb +105 -0
- data/lib/postsvg/translation/record_emitter.rb +35 -0
- data/lib/postsvg/translation.rb +17 -0
- data/lib/postsvg/version.rb +1 -1
- data/lib/postsvg/visitors/ps_visitor/arithmetic.rb +125 -0
- data/lib/postsvg/visitors/ps_visitor/boolean.rb +105 -0
- data/lib/postsvg/visitors/ps_visitor/color.rb +53 -0
- data/lib/postsvg/visitors/ps_visitor/common.rb +66 -0
- data/lib/postsvg/visitors/ps_visitor/container.rb +164 -0
- data/lib/postsvg/visitors/ps_visitor/control_flow.rb +110 -0
- data/lib/postsvg/visitors/ps_visitor/device.rb +20 -0
- data/lib/postsvg/visitors/ps_visitor/dictionary.rb +89 -0
- data/lib/postsvg/visitors/ps_visitor/font.rb +93 -0
- data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +55 -0
- data/lib/postsvg/visitors/ps_visitor/painting.rb +90 -0
- data/lib/postsvg/visitors/ps_visitor/path.rb +112 -0
- data/lib/postsvg/visitors/ps_visitor/stack.rb +47 -0
- data/lib/postsvg/visitors/ps_visitor/transformations.rb +101 -0
- data/lib/postsvg/visitors/ps_visitor.rb +208 -0
- data/lib/postsvg/visitors.rb +9 -0
- data/lib/postsvg.rb +93 -59
- data/lychee.toml +86 -0
- metadata +216 -11
- 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
|