postsvg 0.3.0 → 0.3.1
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/.rubocop_todo.yml +533 -49
- data/CLAUDE.md +27 -3
- data/Gemfile +2 -2
- data/README.adoc +62 -4
- data/Rakefile +17 -0
- data/gemfiles/native-parity/Gemfile +8 -0
- data/lib/postsvg/cli.rb +10 -5
- data/lib/postsvg/graphics_context.rb +2 -1
- data/lib/postsvg/interpreter.rb +1 -2
- data/lib/postsvg/options.rb +1 -1
- data/lib/postsvg/parser/postscript_parser.rb +7 -7
- data/lib/postsvg/renderer.rb +7 -3
- data/lib/postsvg/svg/clip_path_registry.rb +3 -1
- data/lib/postsvg/svg/element.rb +4 -2
- data/lib/postsvg/svg/elements/ellipse.rb +2 -1
- data/lib/postsvg/svg/elements/svg.rb +2 -1
- data/lib/postsvg/svg/paint.rb +1 -1
- data/lib/postsvg/svg/parser.rb +3 -3
- data/lib/postsvg/svg/path_data/parser.rb +10 -6
- data/lib/postsvg/svg/stroke.rb +4 -2
- data/lib/postsvg/svg/transform_list.rb +1 -1
- data/lib/postsvg/svg.rb +1 -1
- data/lib/postsvg/svg_builder.rb +15 -11
- data/lib/postsvg/translation/arc_converter.rb +23 -14
- data/lib/postsvg/translation/bounding_box.rb +7 -4
- data/lib/postsvg/translation/handlers/circle_handler.rb +2 -2
- data/lib/postsvg/translation/handlers/ellipse_handler.rb +8 -3
- data/lib/postsvg/translation/handlers/group_handler.rb +2 -1
- data/lib/postsvg/translation/handlers/image_handler.rb +3 -2
- data/lib/postsvg/translation/handlers/line_handler.rb +6 -2
- data/lib/postsvg/translation/handlers/open_handler.rb +3 -2
- data/lib/postsvg/translation/handlers/path_handler.rb +82 -60
- data/lib/postsvg/translation/handlers/polygon_handler.rb +4 -2
- data/lib/postsvg/translation/handlers/polyline_handler.rb +5 -4
- data/lib/postsvg/translation/handlers/rect_handler.rb +10 -5
- data/lib/postsvg/translation/handlers/shared.rb +11 -11
- data/lib/postsvg/translation/handlers/svg_handler.rb +3 -2
- data/lib/postsvg/translation/handlers/text_handler.rb +17 -8
- data/lib/postsvg/translation/handlers.rb +2 -1
- data/lib/postsvg/translation/ps_renderer.rb +18 -4
- data/lib/postsvg/translation/record_emitter.rb +54 -0
- data/lib/postsvg/version.rb +1 -1
- data/lib/postsvg/visitors/ps_visitor/arithmetic.rb +6 -6
- data/lib/postsvg/visitors/ps_visitor/color.rb +8 -5
- data/lib/postsvg/visitors/ps_visitor/common.rb +1 -2
- data/lib/postsvg/visitors/ps_visitor/container.rb +13 -13
- data/lib/postsvg/visitors/ps_visitor/control_flow.rb +3 -3
- data/lib/postsvg/visitors/ps_visitor/dictionary.rb +8 -4
- data/lib/postsvg/visitors/ps_visitor/font.rb +15 -4
- data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +7 -5
- data/lib/postsvg/visitors/ps_visitor/painting.rb +3 -3
- data/lib/postsvg/visitors/ps_visitor/path.rb +52 -33
- data/lib/postsvg/visitors/ps_visitor/transformations.rb +45 -7
- data/lib/postsvg/visitors/ps_visitor.rb +18 -2
- data/lib/postsvg.rb +43 -38
- metadata +20 -39
- data/lib/postsvg/color.rb +0 -132
- data/lib/postsvg/format_number.rb +0 -22
- data/lib/postsvg/matrix.rb +0 -106
- data/lib/postsvg/model/literals/array.rb +0 -41
- data/lib/postsvg/model/literals/dictionary.rb +0 -34
- data/lib/postsvg/model/literals/hex.rb +0 -37
- data/lib/postsvg/model/literals/name.rb +0 -40
- data/lib/postsvg/model/literals/number.rb +0 -36
- data/lib/postsvg/model/literals/procedure.rb +0 -41
- data/lib/postsvg/model/literals/string.rb +0 -30
- data/lib/postsvg/model/literals.rb +0 -19
- data/lib/postsvg/model/operator.rb +0 -58
- data/lib/postsvg/model/operators/arithmetic.rb +0 -264
- data/lib/postsvg/model/operators/boolean.rb +0 -182
- data/lib/postsvg/model/operators/color.rb +0 -74
- data/lib/postsvg/model/operators/container.rb +0 -186
- data/lib/postsvg/model/operators/control_flow.rb +0 -119
- data/lib/postsvg/model/operators/device.rb +0 -21
- data/lib/postsvg/model/operators/dictionary.rb +0 -118
- data/lib/postsvg/model/operators/font.rb +0 -121
- data/lib/postsvg/model/operators/graphics_state.rb +0 -84
- data/lib/postsvg/model/operators/painting.rb +0 -29
- data/lib/postsvg/model/operators/path.rb +0 -169
- data/lib/postsvg/model/operators/stack.rb +0 -72
- data/lib/postsvg/model/operators/transformations.rb +0 -103
- data/lib/postsvg/model/operators.rb +0 -89
- data/lib/postsvg/model/program.rb +0 -68
- data/lib/postsvg/model/token.rb +0 -43
- data/lib/postsvg/model.rb +0 -17
- data/lib/postsvg/serializer.rb +0 -325
- data/lib/postsvg/source/ast_builder.rb +0 -308
- data/lib/postsvg/source/lexer.rb +0 -322
- data/lib/postsvg/source/operand_stack.rb +0 -55
- data/lib/postsvg/source.rb +0 -21
data/lib/postsvg.rb
CHANGED
|
@@ -1,63 +1,68 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "postscript"
|
|
3
4
|
require "parslet"
|
|
4
5
|
require "thor"
|
|
5
|
-
require "
|
|
6
|
+
require "leptris"
|
|
6
7
|
|
|
7
8
|
module Postsvg
|
|
8
9
|
autoload :VERSION, "postsvg/version"
|
|
9
10
|
|
|
10
|
-
#
|
|
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
|
+
# ============================================================
|
|
11
44
|
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"
|
|
20
45
|
autoload :TranslationError, "postsvg/errors"
|
|
21
46
|
autoload :UnsupportedElementError, "postsvg/errors"
|
|
22
47
|
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"
|
|
28
48
|
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
49
|
+
# Legacy alias for RenderError (postsvg 0.1 API).
|
|
50
|
+
ConversionError = RenderError
|
|
51
|
+
# Legacy alias for UndefinedOperatorError (postsvg 0.1 API).
|
|
52
|
+
UnsupportedOperatorError = UndefinedOperatorError
|
|
33
53
|
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# PS domain model
|
|
38
|
-
autoload :Model, "postsvg/model"
|
|
39
|
-
|
|
40
|
-
# Graphics state
|
|
54
|
+
# ============================================================
|
|
55
|
+
# Rendering / SVG-side types owned by postsvg.
|
|
56
|
+
# ============================================================
|
|
41
57
|
autoload :GraphicsContext, "postsvg/graphics_context"
|
|
42
58
|
autoload :GraphicsStack, "postsvg/graphics_stack"
|
|
43
59
|
autoload :PathBuilder, "postsvg/path_builder"
|
|
44
|
-
|
|
45
|
-
# SVG emission (forward direction)
|
|
46
60
|
autoload :SvgBuilder, "postsvg/svg_builder"
|
|
47
|
-
|
|
48
|
-
# Forward direction (PS -> SVG)
|
|
49
61
|
autoload :Options, "postsvg/options"
|
|
50
62
|
autoload :Renderer, "postsvg/renderer"
|
|
51
63
|
autoload :Visitors, "postsvg/visitors"
|
|
52
|
-
|
|
53
|
-
# SVG domain model (reverse direction input)
|
|
54
64
|
autoload :Svg, "postsvg/svg"
|
|
55
|
-
|
|
56
|
-
# Reverse direction (SVG -> PS)
|
|
57
65
|
autoload :Translation, "postsvg/translation"
|
|
58
|
-
autoload :Serializer, "postsvg/serializer"
|
|
59
|
-
|
|
60
|
-
# CLI
|
|
61
66
|
autoload :CLI, "postsvg/cli"
|
|
62
67
|
|
|
63
68
|
# Legacy implementations retained on disk for reference. Not on the
|
|
@@ -73,7 +78,7 @@ module Postsvg
|
|
|
73
78
|
class << self
|
|
74
79
|
# PS/EPS source -> SVG string.
|
|
75
80
|
def to_svg(source, **options)
|
|
76
|
-
program = Source.parse(source)
|
|
81
|
+
program = Postscript::Source.parse(source)
|
|
77
82
|
Renderer.call(program, Options.new(**options))
|
|
78
83
|
end
|
|
79
84
|
|
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.3.
|
|
4
|
+
version: 0.3.1
|
|
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-
|
|
11
|
+
date: 2026-11-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: leptris
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
19
|
+
version: 1.9.399.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:
|
|
26
|
+
version: 1.9.399.0
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: parslet
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -38,6 +38,20 @@ 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'
|
|
41
55
|
- !ruby/object:Gem::Dependency
|
|
42
56
|
name: thor
|
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -172,55 +186,22 @@ files:
|
|
|
172
186
|
- docs/troubleshooting.adoc
|
|
173
187
|
- docs/validation.adoc
|
|
174
188
|
- exe/postsvg
|
|
189
|
+
- gemfiles/native-parity/Gemfile
|
|
175
190
|
- lib/postsvg.rb
|
|
176
191
|
- lib/postsvg/cli.rb
|
|
177
|
-
- lib/postsvg/color.rb
|
|
178
192
|
- lib/postsvg/colors.rb
|
|
179
193
|
- lib/postsvg/converter.rb
|
|
180
194
|
- lib/postsvg/errors.rb
|
|
181
|
-
- lib/postsvg/format_number.rb
|
|
182
195
|
- lib/postsvg/graphics_context.rb
|
|
183
196
|
- lib/postsvg/graphics_stack.rb
|
|
184
197
|
- lib/postsvg/graphics_state.rb
|
|
185
198
|
- 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
|
|
213
199
|
- lib/postsvg/options.rb
|
|
214
200
|
- lib/postsvg/parser.rb
|
|
215
201
|
- lib/postsvg/parser/postscript_parser.rb
|
|
216
202
|
- lib/postsvg/parser/transform.rb
|
|
217
203
|
- lib/postsvg/path_builder.rb
|
|
218
204
|
- 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
|
|
224
205
|
- lib/postsvg/svg.rb
|
|
225
206
|
- lib/postsvg/svg/attribute_parser.rb
|
|
226
207
|
- lib/postsvg/svg/clip_path_registry.rb
|
data/lib/postsvg/color.rb
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Postsvg
|
|
4
|
-
# Immutable RGB color value object. Use the constructors (+.rgb+,
|
|
5
|
-
# +.gray+, +.cmyk+, +.parse+) instead of +new+ when constructing from
|
|
6
|
-
# other color models; they normalize to integer [0, 255] channels.
|
|
7
|
-
class Color
|
|
8
|
-
include Comparable
|
|
9
|
-
|
|
10
|
-
attr_reader :red, :green, :blue
|
|
11
|
-
|
|
12
|
-
def self.clamp_byte(value)
|
|
13
|
-
Integer === value ? value.clamp(0, 255) : value.to_i.clamp(0, 255)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def self.scale_unit_to_byte(value)
|
|
17
|
-
(value.clamp(0.0, 1.0) * 255.0).round
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def initialize(red, green, blue)
|
|
21
|
-
@red = Color.clamp_byte(red)
|
|
22
|
-
@green = Color.clamp_byte(green)
|
|
23
|
-
@blue = Color.clamp_byte(blue)
|
|
24
|
-
freeze
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# RGB triple in PS-native [0.0, 1.0] floats.
|
|
28
|
-
def self.rgb(r, g, b)
|
|
29
|
-
new(scale_unit_to_byte(r), scale_unit_to_byte(g), scale_unit_to_byte(b))
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Gray in [0.0, 1.0].
|
|
33
|
-
def self.gray(level)
|
|
34
|
-
v = scale_unit_to_byte(level)
|
|
35
|
-
new(v, v, v)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# CMYK in [0.0, 1.0]. Conversion matches PLRM §8.2.4 simplified
|
|
39
|
-
# formula; not colorimetrically exact.
|
|
40
|
-
def self.cmyk(c, m, y, k)
|
|
41
|
-
new(
|
|
42
|
-
scale_unit_to_byte((1 - c) * (1 - k)),
|
|
43
|
-
scale_unit_to_byte((1 - m) * (1 - k)),
|
|
44
|
-
scale_unit_to_byte((1 - y) * (1 - k)),
|
|
45
|
-
)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Parse CSS / SVG color notations: #rgb, #rrggbb, rgb(r, g, b),
|
|
49
|
-
# rgb(r g b / a), named colors (X11 subset).
|
|
50
|
-
def self.parse(text)
|
|
51
|
-
return text if text.is_a?(Color)
|
|
52
|
-
|
|
53
|
-
case text.to_s.downcase
|
|
54
|
-
when /\A#([0-9a-f]{6})\z/
|
|
55
|
-
hex = ::Regexp.last_match(1)
|
|
56
|
-
new(hex[0, 2].to_i(16), hex[2, 2].to_i(16), hex[4, 2].to_i(16))
|
|
57
|
-
when /\A#([0-9a-f]{3})\z/
|
|
58
|
-
hex = ::Regexp.last_match(1)
|
|
59
|
-
new((hex[0] * 2).to_i(16), (hex[1] * 2).to_i(16), (hex[2] * 2).to_i(16))
|
|
60
|
-
when /\argb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/
|
|
61
|
-
new(::Regexp.last_match(1).to_i, ::Regexp.last_match(2).to_i, ::Regexp.last_match(3).to_i)
|
|
62
|
-
when /\argb\(\s*(\d+)\s+(\d+)\s+(\d+)/
|
|
63
|
-
new(::Regexp.last_match(1).to_i, ::Regexp.last_match(2).to_i, ::Regexp.last_match(3).to_i)
|
|
64
|
-
when "none", "transparent"
|
|
65
|
-
nil
|
|
66
|
-
else
|
|
67
|
-
rgb = NAMED_COLORS[text.to_s]
|
|
68
|
-
return new(*rgb) if rgb
|
|
69
|
-
|
|
70
|
-
raise ArgumentError, "unrecognized color: #{text.inspect}"
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def gray?
|
|
75
|
-
red == green && green == blue
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def gray_level
|
|
79
|
-
(red + green + blue) / 765.0
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def to_rgb_unit
|
|
83
|
-
[red / 255.0, green / 255.0, blue / 255.0]
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def to_svg
|
|
87
|
-
format("#%02x%02x%02x", red, green, blue)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def to_ps_rgb
|
|
91
|
-
to_rgb_unit.map { |c| FormatNumber.call(c) }.join(" ")
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def to_ps_gray
|
|
95
|
-
raise ArgumentError, "color is not gray" unless gray?
|
|
96
|
-
|
|
97
|
-
FormatNumber.call(gray_level)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def <=>(other)
|
|
101
|
-
return nil unless other.is_a?(Color)
|
|
102
|
-
|
|
103
|
-
[red, green, blue] <=> [other.red, other.green, other.blue]
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def hash
|
|
107
|
-
[red, green, blue].hash
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def eql?(other)
|
|
111
|
-
other.is_a?(Color) && other.red == red && other.green == green && other.blue == blue
|
|
112
|
-
end
|
|
113
|
-
alias == eql?
|
|
114
|
-
|
|
115
|
-
NAMED_COLORS = {
|
|
116
|
-
"black" => [0, 0, 0], "white" => [255, 255, 255],
|
|
117
|
-
"red" => [255, 0, 0], "green" => [0, 128, 0], "blue" => [0, 0, 255],
|
|
118
|
-
"yellow" => [255, 255, 0], "cyan" => [0, 255, 255],
|
|
119
|
-
"magenta" => [255, 0, 255], "gray" => [128, 128, 128],
|
|
120
|
-
"grey" => [128, 128, 128], "silver" => [192, 192, 192],
|
|
121
|
-
"maroon" => [128, 0, 0], "olive" => [128, 128, 0],
|
|
122
|
-
"navy" => [0, 0, 128], "purple" => [128, 0, 128],
|
|
123
|
-
"teal" => [0, 128, 128], "lime" => [0, 255, 0],
|
|
124
|
-
"aqua" => [0, 255, 255], "fuchsia" => [255, 0, 255],
|
|
125
|
-
"orange" => [255, 165, 0], "pink" => [255, 192, 203],
|
|
126
|
-
"brown" => [165, 42, 42],
|
|
127
|
-
}.freeze
|
|
128
|
-
|
|
129
|
-
BLACK = Color.new(0, 0, 0).freeze
|
|
130
|
-
WHITE = Color.new(255, 255, 255).freeze
|
|
131
|
-
end
|
|
132
|
-
end
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Postsvg
|
|
4
|
-
# Single source of truth for formatting numbers in both directions.
|
|
5
|
-
#
|
|
6
|
-
# - Integers print without a trailing ".0".
|
|
7
|
-
# - Floats print to 4 decimals, trailing zeros stripped.
|
|
8
|
-
# - -0 normalizes to 0 (so viewBoxes don't read "0 -0 100 100").
|
|
9
|
-
# - +Inf / -Inf / NaN raise ArgumentError; callers should validate.
|
|
10
|
-
module FormatNumber
|
|
11
|
-
module_function
|
|
12
|
-
|
|
13
|
-
def call(value)
|
|
14
|
-
raise ArgumentError, "non-finite number: #{value.inspect}" unless value.finite?
|
|
15
|
-
|
|
16
|
-
normalized = value.zero? ? 0.0 : value
|
|
17
|
-
return normalized.to_i.to_s if normalized == normalized.to_i
|
|
18
|
-
|
|
19
|
-
format("%.4f", normalized).sub(/\.?0+$/, "")
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
data/lib/postsvg/matrix.rb
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Postsvg
|
|
4
|
-
# Matrix transformation class for PostScript coordinate transformations
|
|
5
|
-
# Implements affine transformation matrix [a b c d e f]
|
|
6
|
-
class Matrix
|
|
7
|
-
attr_accessor :a, :b, :c, :d, :e, :f
|
|
8
|
-
|
|
9
|
-
def initialize(a: 1, b: 0, c: 0, d: 1, e: 0, f: 0)
|
|
10
|
-
@a = a
|
|
11
|
-
@b = b
|
|
12
|
-
@c = c
|
|
13
|
-
@d = d
|
|
14
|
-
@e = e
|
|
15
|
-
@f = f
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def multiply(matrix)
|
|
19
|
-
result = Matrix.new
|
|
20
|
-
result.a = (@a * matrix.a) + (@c * matrix.b)
|
|
21
|
-
result.b = (@b * matrix.a) + (@d * matrix.b)
|
|
22
|
-
result.c = (@a * matrix.c) + (@c * matrix.d)
|
|
23
|
-
result.d = (@b * matrix.c) + (@d * matrix.d)
|
|
24
|
-
result.e = (@a * matrix.e) + (@c * matrix.f) + @e
|
|
25
|
-
result.f = (@b * matrix.e) + (@d * matrix.f) + @f
|
|
26
|
-
result
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def translate(tx, ty)
|
|
30
|
-
multiply(Matrix.new(e: tx, f: ty))
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def scale(sx, sy)
|
|
34
|
-
multiply(Matrix.new(a: sx, d: sy))
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def rotate(degrees)
|
|
38
|
-
radians = degrees * Math::PI / 180.0
|
|
39
|
-
m = Matrix.new
|
|
40
|
-
m.a = Math.cos(radians)
|
|
41
|
-
m.b = Math.sin(radians)
|
|
42
|
-
m.c = -Math.sin(radians)
|
|
43
|
-
m.d = Math.cos(radians)
|
|
44
|
-
multiply(m)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def skew_x(angle)
|
|
48
|
-
radians = angle * Math::PI / 180.0
|
|
49
|
-
multiply(Matrix.new(c: Math.tan(radians)))
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def skew_y(angle)
|
|
53
|
-
radians = angle * Math::PI / 180.0
|
|
54
|
-
multiply(Matrix.new(b: Math.tan(radians)))
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def to_transform_string
|
|
58
|
-
"matrix(#{@a} #{@b} #{@c} #{@d} #{@e} #{@f})"
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def apply_point(x, y)
|
|
62
|
-
{
|
|
63
|
-
x: (x * @a) + (y * @c) + @e,
|
|
64
|
-
y: (x * @b) + (y * @d) + @f,
|
|
65
|
-
}
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def decompose
|
|
69
|
-
scale_x = Math.hypot(@a, @b)
|
|
70
|
-
scale_y = ((@a * @d) - (@b * @c)) / scale_x
|
|
71
|
-
|
|
72
|
-
rotation = Math.atan2(@b, @a) * (180.0 / Math::PI)
|
|
73
|
-
|
|
74
|
-
skew_x = Math.atan2((@a * @c) + (@b * @d), scale_x * scale_x)
|
|
75
|
-
skew_y = Math.atan2((@a * @b) + (@c * @d), scale_y * scale_y)
|
|
76
|
-
|
|
77
|
-
{
|
|
78
|
-
translate: { x: @e, y: @f },
|
|
79
|
-
scale: { x: scale_x, y: scale_y },
|
|
80
|
-
rotate: rotation,
|
|
81
|
-
skew: {
|
|
82
|
-
x: skew_x * (180.0 / Math::PI),
|
|
83
|
-
y: skew_y * (180.0 / Math::PI),
|
|
84
|
-
},
|
|
85
|
-
}
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def invert
|
|
89
|
-
det = (@a * @d) - (@b * @c)
|
|
90
|
-
return Matrix.new if det.abs < 1e-10
|
|
91
|
-
|
|
92
|
-
inv = Matrix.new
|
|
93
|
-
inv.a = @d / det
|
|
94
|
-
inv.b = -@b / det
|
|
95
|
-
inv.c = -@c / det
|
|
96
|
-
inv.d = @a / det
|
|
97
|
-
inv.e = ((@c * @f) - (@d * @e)) / det
|
|
98
|
-
inv.f = ((@b * @e) - (@a * @f)) / det
|
|
99
|
-
inv
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def identity?
|
|
103
|
-
@a == 1 && @b.zero? && @c.zero? && @d == 1 && @e.zero? && @f.zero?
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Postsvg
|
|
4
|
-
module Model
|
|
5
|
-
module Literals
|
|
6
|
-
# Bracketed array literal: +[1 2 3]+. Elements may be any
|
|
7
|
-
# literal or operator.
|
|
8
|
-
class ArrayLiteral
|
|
9
|
-
include Enumerable
|
|
10
|
-
|
|
11
|
-
attr_reader :elements
|
|
12
|
-
|
|
13
|
-
def initialize(elements = [])
|
|
14
|
-
@elements = elements.freeze
|
|
15
|
-
freeze
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def each(&block)
|
|
19
|
-
@elements.each(&block)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def length = @elements.length
|
|
23
|
-
def empty? = @elements.empty?
|
|
24
|
-
def [](idx) = @elements[idx]
|
|
25
|
-
|
|
26
|
-
def accept(visitor, ctx)
|
|
27
|
-
visitor.visit_array(self, ctx)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def ==(other)
|
|
31
|
-
other.is_a?(ArrayLiteral) && other.elements == @elements
|
|
32
|
-
end
|
|
33
|
-
alias eql? ==
|
|
34
|
-
|
|
35
|
-
def hash
|
|
36
|
-
@elements.hash
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Postsvg
|
|
4
|
-
module Model
|
|
5
|
-
module Literals
|
|
6
|
-
# Inline dictionary literal: +<< /Key (value) /Num 42 >>+.
|
|
7
|
-
class Dictionary
|
|
8
|
-
attr_reader :entries
|
|
9
|
-
|
|
10
|
-
def initialize(entries = {})
|
|
11
|
-
@entries = entries.dup.freeze
|
|
12
|
-
freeze
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def [](key) = @entries[key]
|
|
16
|
-
def key?(key) = @entries.key?(key)
|
|
17
|
-
def each(&block) = @entries.each(&block)
|
|
18
|
-
|
|
19
|
-
def accept(visitor, ctx)
|
|
20
|
-
visitor.visit_dictionary(self, ctx)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def ==(other)
|
|
24
|
-
other.is_a?(Dictionary) && other.entries == @entries
|
|
25
|
-
end
|
|
26
|
-
alias eql? ==
|
|
27
|
-
|
|
28
|
-
def hash
|
|
29
|
-
@entries.hash
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Postsvg
|
|
4
|
-
module Model
|
|
5
|
-
module Literals
|
|
6
|
-
# Hex-string literal: +<DEADBEEF>+.
|
|
7
|
-
class HexLiteral
|
|
8
|
-
attr_reader :value
|
|
9
|
-
|
|
10
|
-
def initialize(value)
|
|
11
|
-
@value = value.to_s
|
|
12
|
-
freeze
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
# Raw bytes encoded by the hex literal.
|
|
16
|
-
def bytes
|
|
17
|
-
hex = value.delete("^0-9a-fA-F")
|
|
18
|
-
hex << "0" if hex.length.odd?
|
|
19
|
-
hex.chars.each_slice(2).map { |pair| pair.join.to_i(16) }.pack("C*")
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def accept(visitor, ctx)
|
|
23
|
-
visitor.visit_hex(self, ctx)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def ==(other)
|
|
27
|
-
other.is_a?(HexLiteral) && other.value == value
|
|
28
|
-
end
|
|
29
|
-
alias eql? ==
|
|
30
|
-
|
|
31
|
-
def hash
|
|
32
|
-
value.hash
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Postsvg
|
|
4
|
-
module Model
|
|
5
|
-
module Literals
|
|
6
|
-
# A PostScript name. +literal:+ distinguishes +/foo+ (literal,
|
|
7
|
-
# pushed onto the stack) from +foo+ (executable, looked up and
|
|
8
|
-
# executed).
|
|
9
|
-
class Name
|
|
10
|
-
attr_reader :value, :literal
|
|
11
|
-
|
|
12
|
-
def initialize(value, literal: false)
|
|
13
|
-
@value = value.to_s
|
|
14
|
-
@literal = literal
|
|
15
|
-
freeze
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def literal? = literal
|
|
19
|
-
def executable? = !literal
|
|
20
|
-
|
|
21
|
-
def to_s
|
|
22
|
-
@value
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def accept(visitor, ctx)
|
|
26
|
-
visitor.visit_name(self, ctx)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def ==(other)
|
|
30
|
-
other.is_a?(Name) && other.value == value && other.literal == literal
|
|
31
|
-
end
|
|
32
|
-
alias eql? ==
|
|
33
|
-
|
|
34
|
-
def hash
|
|
35
|
-
[value, literal].hash
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Postsvg
|
|
4
|
-
module Model
|
|
5
|
-
module Literals
|
|
6
|
-
# A PostScript numeric literal. Stored as a Ruby Numeric so
|
|
7
|
-
# arithmetic Just Works; class carries the original source
|
|
8
|
-
# representation choice (int vs float) when known.
|
|
9
|
-
class Number
|
|
10
|
-
attr_reader :value
|
|
11
|
-
|
|
12
|
-
def initialize(value)
|
|
13
|
-
@value = value.is_a?(Numeric) ? value : Float(value)
|
|
14
|
-
freeze
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def to_i = value.to_i
|
|
18
|
-
def to_f = value.to_f
|
|
19
|
-
def integer? = value.is_a?(Integer)
|
|
20
|
-
|
|
21
|
-
def accept(visitor, ctx)
|
|
22
|
-
visitor.visit_number(self, ctx)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def ==(other)
|
|
26
|
-
other.is_a?(Number) && other.value == value
|
|
27
|
-
end
|
|
28
|
-
alias eql? ==
|
|
29
|
-
|
|
30
|
-
def hash
|
|
31
|
-
value.hash
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|