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
|
@@ -15,8 +15,8 @@ module Postsvg
|
|
|
15
15
|
cy = element.cy
|
|
16
16
|
if r.to_f.positive?
|
|
17
17
|
context.emitter.emit(Model::Operators::Path::Arc.new(
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
x: cx, y: cy, radius: r, angle1: 0, angle2: 360,
|
|
19
|
+
))
|
|
20
20
|
end
|
|
21
21
|
emit_paint(element, context)
|
|
22
22
|
context.emitter.emit(Model::Operators::GraphicsState::Grestore.new)
|
|
@@ -17,9 +17,14 @@ module Postsvg
|
|
|
17
17
|
# Approximate ellipse with scale + arc.
|
|
18
18
|
if rx.to_f.positive? && ry.to_f.positive?
|
|
19
19
|
scale = rx / ry
|
|
20
|
-
context.emitter.emit(Model::Operators::Transformations::Translate.new(
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
context.emitter.emit(Model::Operators::Transformations::Translate.new(
|
|
21
|
+
tx: cx, ty: cy,
|
|
22
|
+
))
|
|
23
|
+
context.emitter.emit(Model::Operators::Transformations::Scale.new(
|
|
24
|
+
sx: scale, sy: 1,
|
|
25
|
+
))
|
|
26
|
+
context.emitter.emit(Model::Operators::Path::Arc.new(x: 0, y: 0,
|
|
27
|
+
radius: ry, angle1: 0, angle2: 360))
|
|
23
28
|
end
|
|
24
29
|
emit_paint(element, context)
|
|
25
30
|
context.emitter.emit(Model::Operators::GraphicsState::Grestore.new)
|
|
@@ -11,7 +11,8 @@ module Postsvg
|
|
|
11
11
|
emit_transform(element, context)
|
|
12
12
|
emit_paint_setup(element, context)
|
|
13
13
|
element.children.each do |child|
|
|
14
|
-
Postsvg::Translation::PsRenderer::DEFAULT_REGISTRY.translate(child,
|
|
14
|
+
Postsvg::Translation::PsRenderer::DEFAULT_REGISTRY.translate(child,
|
|
15
|
+
context)
|
|
15
16
|
end
|
|
16
17
|
context.emitter.emit(Model::Operators::GraphicsState::Grestore.new)
|
|
17
18
|
end
|
|
@@ -10,9 +10,10 @@ module Postsvg
|
|
|
10
10
|
# Raster image support is tracked in TODO.roadmap/21-images.md.
|
|
11
11
|
# For now, emit a comment so the serialized PS file has a
|
|
12
12
|
# visible marker of the elided content.
|
|
13
|
-
|
|
13
|
+
element.href || "(none)"
|
|
14
14
|
context.emitter.emit(Model::UnknownOperator.new(keyword: "image"))
|
|
15
|
-
expand_bbox(context, element.x, element.y, element.x + element.width,
|
|
15
|
+
expand_bbox(context, element.x, element.y, element.x + element.width,
|
|
16
|
+
element.y + element.height)
|
|
16
17
|
end
|
|
17
18
|
end
|
|
18
19
|
end
|
|
@@ -11,8 +11,12 @@ module Postsvg
|
|
|
11
11
|
emit_transform(element, context)
|
|
12
12
|
emit_stroke(element, context)
|
|
13
13
|
context.emitter.emit(Model::Operators::Path::Newpath.new)
|
|
14
|
-
context.emitter.emit(Model::Operators::Path::Moveto.new(
|
|
15
|
-
|
|
14
|
+
context.emitter.emit(Model::Operators::Path::Moveto.new(
|
|
15
|
+
x: element.x1, y: element.y1,
|
|
16
|
+
))
|
|
17
|
+
context.emitter.emit(Model::Operators::Path::Lineto.new(
|
|
18
|
+
x: element.x2, y: element.y2,
|
|
19
|
+
))
|
|
16
20
|
context.emitter.emit(Model::Operators::Painting::Stroke.new)
|
|
17
21
|
context.emitter.emit(Model::Operators::GraphicsState::Grestore.new)
|
|
18
22
|
expand_bbox(context, element.x1, element.y1, element.x2, element.y2)
|
|
@@ -9,10 +9,11 @@ module Postsvg
|
|
|
9
9
|
# getter, so no capability check is needed.
|
|
10
10
|
def self.call(element, context)
|
|
11
11
|
element.children.each do |child|
|
|
12
|
-
Postsvg::Translation::PsRenderer.default_registry.translate(child,
|
|
12
|
+
Postsvg::Translation::PsRenderer.default_registry.translate(child,
|
|
13
|
+
context)
|
|
13
14
|
end
|
|
14
15
|
end
|
|
15
16
|
end
|
|
16
17
|
end
|
|
17
18
|
end
|
|
18
|
-
end
|
|
19
|
+
end
|
|
@@ -9,8 +9,8 @@ module Postsvg
|
|
|
9
9
|
#
|
|
10
10
|
# - M/m moveto (relative repeats as l)
|
|
11
11
|
# - L/l lineto
|
|
12
|
-
# - H/h
|
|
13
|
-
# - V/v
|
|
12
|
+
# - H/h horizontal line
|
|
13
|
+
# - V/v vertical line
|
|
14
14
|
# - C/c cubic bezier
|
|
15
15
|
# - S/s smooth cubic (reflects previous control point)
|
|
16
16
|
# - Q/q quadratic bezier (converted to cubic)
|
|
@@ -22,7 +22,8 @@ module Postsvg
|
|
|
22
22
|
|
|
23
23
|
# Control-point reflection state for smooth curves. +nil+ when
|
|
24
24
|
# the previous command wasn't a smooth-eligible curve.
|
|
25
|
-
SmoothState = Struct.new(:control_x, :control_y, :smoothable,
|
|
25
|
+
SmoothState = Struct.new(:control_x, :control_y, :smoothable,
|
|
26
|
+
keyword_init: true)
|
|
26
27
|
|
|
27
28
|
def self.call(element, context)
|
|
28
29
|
context.emitter.emit(Model::Operators::GraphicsState::Gsave.new)
|
|
@@ -30,7 +31,8 @@ module Postsvg
|
|
|
30
31
|
context.emitter.emit(Model::Operators::Path::Newpath.new)
|
|
31
32
|
|
|
32
33
|
state = PathState.new
|
|
33
|
-
smooth = SmoothState.new(control_x: nil, control_y: nil,
|
|
34
|
+
smooth = SmoothState.new(control_x: nil, control_y: nil,
|
|
35
|
+
smoothable: false)
|
|
34
36
|
element.commands.each do |cmd|
|
|
35
37
|
dispatch_command(cmd, state, smooth, context)
|
|
36
38
|
end
|
|
@@ -48,7 +50,7 @@ module Postsvg
|
|
|
48
50
|
when "H", "h"
|
|
49
51
|
handle_hv(cmd, state, context, horizontal: cmd.opcode.upcase == "H")
|
|
50
52
|
when "V", "v"
|
|
51
|
-
handle_hv(cmd, state, context, horizontal:
|
|
53
|
+
handle_hv(cmd, state, context, horizontal: false)
|
|
52
54
|
when "C", "c"
|
|
53
55
|
handle_cubic(cmd, state, smooth, context)
|
|
54
56
|
when "S", "s"
|
|
@@ -74,7 +76,8 @@ module Postsvg
|
|
|
74
76
|
context.emitter.emit(Model::Operators::Path::Moveto.new(x: x, y: y))
|
|
75
77
|
else
|
|
76
78
|
state.move_to_rel(x, y)
|
|
77
|
-
context.emitter.emit(Model::Operators::Path::Rmoveto.new(dx: x,
|
|
79
|
+
context.emitter.emit(Model::Operators::Path::Rmoveto.new(dx: x,
|
|
80
|
+
dy: y))
|
|
78
81
|
end
|
|
79
82
|
smooth.smoothable = false
|
|
80
83
|
end
|
|
@@ -86,7 +89,8 @@ module Postsvg
|
|
|
86
89
|
context.emitter.emit(Model::Operators::Path::Lineto.new(x: x, y: y))
|
|
87
90
|
else
|
|
88
91
|
state.line_to_rel(x, y)
|
|
89
|
-
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: x,
|
|
92
|
+
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: x,
|
|
93
|
+
dy: y))
|
|
90
94
|
end
|
|
91
95
|
end
|
|
92
96
|
|
|
@@ -96,18 +100,23 @@ module Postsvg
|
|
|
96
100
|
if horizontal
|
|
97
101
|
new_x = v
|
|
98
102
|
state.line_to(new_x, state.current_y)
|
|
99
|
-
context.emitter.emit(Model::Operators::Path::Lineto.new(x: new_x,
|
|
103
|
+
context.emitter.emit(Model::Operators::Path::Lineto.new(x: new_x,
|
|
104
|
+
y: state.current_y))
|
|
100
105
|
else
|
|
101
106
|
new_y = v
|
|
102
107
|
state.line_to(state.current_x, new_y)
|
|
103
|
-
context.emitter.emit(Model::Operators::Path::Lineto.new(
|
|
108
|
+
context.emitter.emit(Model::Operators::Path::Lineto.new(
|
|
109
|
+
x: state.current_x, y: new_y,
|
|
110
|
+
))
|
|
104
111
|
end
|
|
105
112
|
elsif horizontal
|
|
106
113
|
state.line_to_rel(v, 0)
|
|
107
|
-
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: v,
|
|
114
|
+
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: v,
|
|
115
|
+
dy: 0))
|
|
108
116
|
else
|
|
109
117
|
state.line_to_rel(0, v)
|
|
110
|
-
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: 0,
|
|
118
|
+
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: 0,
|
|
119
|
+
dy: v))
|
|
111
120
|
end
|
|
112
121
|
end
|
|
113
122
|
|
|
@@ -116,8 +125,8 @@ module Postsvg
|
|
|
116
125
|
x1, y1, x2, y2, x3, y3 = adapt_curve_args(args, cmd.opcode, state)
|
|
117
126
|
state.curve_to(x3, y3)
|
|
118
127
|
context.emitter.emit(Model::Operators::Path::Curveto.new(
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
x1: x1, y1: y1, x2: x2, y2: y2, x3: x3, y3: y3,
|
|
129
|
+
))
|
|
121
130
|
smooth.control_x = x2
|
|
122
131
|
smooth.control_y = y2
|
|
123
132
|
smooth.smoothable = true
|
|
@@ -129,14 +138,15 @@ module Postsvg
|
|
|
129
138
|
x1, y1 = reflect_control(smooth, state)
|
|
130
139
|
state.curve_to(x3, y3)
|
|
131
140
|
context.emitter.emit(Model::Operators::Path::Curveto.new(
|
|
132
|
-
|
|
133
|
-
|
|
141
|
+
x1: x1, y1: y1, x2: x2, y2: y2, x3: x3, y3: y3,
|
|
142
|
+
))
|
|
134
143
|
else
|
|
135
144
|
dx1, dy1 = reflect_control_rel(smooth, state)
|
|
136
|
-
state.curve_to_rel(dx2: x2, dy2: y2, dx3: x3, dy3: y3, dx1: dx1,
|
|
145
|
+
state.curve_to_rel(dx2: x2, dy2: y2, dx3: x3, dy3: y3, dx1: dx1,
|
|
146
|
+
dy1: dy1)
|
|
137
147
|
context.emitter.emit(Model::Operators::Path::Rcurveto.new(
|
|
138
|
-
|
|
139
|
-
|
|
148
|
+
dx1: dx1, dy1: dy1, dx2: x2, dy2: y2, dx3: x3, dy3: y3,
|
|
149
|
+
))
|
|
140
150
|
x2 = state.current_x - x2
|
|
141
151
|
y2 = state.current_y - y2
|
|
142
152
|
end
|
|
@@ -150,15 +160,16 @@ module Postsvg
|
|
|
150
160
|
qx, qy, x3, y3 = adapt_quadratic_args(args, cmd.opcode, state)
|
|
151
161
|
# Convert quadratic to cubic: c1 = p0 + 2/3*(q-p0),
|
|
152
162
|
# c2 = p3 + 2/3*(q-p3).
|
|
153
|
-
p0x
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
163
|
+
p0x = state.current_x
|
|
164
|
+
p0y = state.current_y
|
|
165
|
+
c1x = p0x + ((2.0 / 3.0) * (qx - p0x))
|
|
166
|
+
c1y = p0y + ((2.0 / 3.0) * (qy - p0y))
|
|
167
|
+
c2x = x3 + ((2.0 / 3.0) * (qx - x3))
|
|
168
|
+
c2y = y3 + ((2.0 / 3.0) * (qy - y3))
|
|
158
169
|
state.curve_to(x3, y3)
|
|
159
170
|
context.emitter.emit(Model::Operators::Path::Curveto.new(
|
|
160
|
-
|
|
161
|
-
|
|
171
|
+
x1: c1x, y1: c1y, x2: c2x, y2: c2y, x3: x3, y3: y3,
|
|
172
|
+
))
|
|
162
173
|
smooth.control_x = qx
|
|
163
174
|
smooth.control_y = qy
|
|
164
175
|
smooth.smoothable = true
|
|
@@ -166,28 +177,27 @@ module Postsvg
|
|
|
166
177
|
|
|
167
178
|
def self.handle_smooth_quadratic(cmd, state, smooth, context)
|
|
168
179
|
x3, y3 = cmd.args
|
|
180
|
+
if cmd.opcode == "t"
|
|
181
|
+
x3 += state.current_x
|
|
182
|
+
y3 += state.current_y
|
|
183
|
+
end
|
|
169
184
|
qx, qy =
|
|
170
185
|
if smooth.smoothable
|
|
171
|
-
[reflect_x(smooth.control_x, state),
|
|
186
|
+
[reflect_x(smooth.control_x, state),
|
|
187
|
+
reflect_y(smooth.control_y, state)]
|
|
172
188
|
else
|
|
173
189
|
[state.current_x, state.current_y]
|
|
174
190
|
end
|
|
175
|
-
p0x
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
else
|
|
183
|
-
state.curve_to_rel(x3 - p0x, y3 - p0y) # treat as rel target
|
|
184
|
-
# Re-anchor to absolute target for emission below.
|
|
185
|
-
state.current_x = p0x + x3
|
|
186
|
-
state.current_y = p0y + y3
|
|
187
|
-
end
|
|
191
|
+
p0x = state.current_x
|
|
192
|
+
p0y = state.current_y
|
|
193
|
+
c1x = p0x + ((2.0 / 3.0) * (qx - p0x))
|
|
194
|
+
c1y = p0y + ((2.0 / 3.0) * (qy - p0y))
|
|
195
|
+
c2x = x3 + ((2.0 / 3.0) * (qx - x3))
|
|
196
|
+
c2y = y3 + ((2.0 / 3.0) * (qy - y3))
|
|
197
|
+
state.curve_to(x3, y3)
|
|
188
198
|
context.emitter.emit(Model::Operators::Path::Curveto.new(
|
|
189
|
-
|
|
190
|
-
|
|
199
|
+
x1: c1x, y1: c1y, x2: c2x, y2: c2y, x3: state.current_x, y3: state.current_y,
|
|
200
|
+
))
|
|
191
201
|
smooth.control_x = qx
|
|
192
202
|
smooth.control_y = qy
|
|
193
203
|
smooth.smoothable = true
|
|
@@ -198,32 +208,43 @@ module Postsvg
|
|
|
198
208
|
# W3C-endorsed endpoint-to-center conversion.
|
|
199
209
|
def self.handle_arc(cmd, state, context)
|
|
200
210
|
rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, x, y = cmd.args
|
|
211
|
+
if cmd.opcode == "a"
|
|
212
|
+
x += state.current_x
|
|
213
|
+
y += state.current_y
|
|
214
|
+
end
|
|
215
|
+
# SVG omits arcs whose endpoints coincide.
|
|
216
|
+
return if [x, y] == [state.current_x, state.current_y]
|
|
217
|
+
|
|
201
218
|
cx, cy, normalized_rx, normalized_ry, psi, theta1, theta2 =
|
|
202
219
|
Postsvg::Translation::ArcConverter.endpoint_to_center(
|
|
203
220
|
x1: state.current_x, y1: state.current_y,
|
|
204
221
|
rx: rx, ry: ry, x_axis_rotation: x_axis_rotation,
|
|
205
222
|
large_arc: large_arc_flag != 0, sweep: sweep_flag != 0,
|
|
206
|
-
x2: x, y2: y
|
|
223
|
+
x2: x, y2: y
|
|
207
224
|
)
|
|
208
|
-
#
|
|
209
|
-
|
|
210
|
-
# transforms around the arc.
|
|
225
|
+
# Restore only the CTM: grestore would discard the appended arc.
|
|
226
|
+
arc_class = sweep_flag.zero? ? Model::Operators::Path::Arcn : Model::Operators::Path::Arc
|
|
211
227
|
if x_axis_rotation.abs > 1e-6 || (rx - ry).abs > 1e-6
|
|
212
|
-
context.emitter.emit(Model::Operators::
|
|
228
|
+
context.emitter.emit(Model::Operators::Transformations::Matrix.new)
|
|
229
|
+
context.emitter.emit(Model::Operators::Transformations::Currentmatrix.new)
|
|
213
230
|
context.emitter.emit(Model::Operators::Transformations::Concat.new(matrix: [
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
context.emitter.emit(Model::Operators::Transformations::Scale.new(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
231
|
+
Math.cos(psi), Math.sin(psi), -Math.sin(psi), Math.cos(psi), cx, cy
|
|
232
|
+
]))
|
|
233
|
+
context.emitter.emit(Model::Operators::Transformations::Scale.new(
|
|
234
|
+
sx: normalized_rx, sy: normalized_ry,
|
|
235
|
+
))
|
|
236
|
+
context.emitter.emit(arc_class.new(
|
|
237
|
+
x: 0, y: 0, radius: 1.0, angle1: theta1, angle2: theta2,
|
|
238
|
+
))
|
|
239
|
+
context.emitter.emit(Model::Operators::Transformations::Setmatrix.new(
|
|
240
|
+
matrix: Model::Computed.new(operator_keyword: "currentmatrix"),
|
|
241
|
+
))
|
|
221
242
|
else
|
|
222
|
-
context.emitter.emit(
|
|
223
|
-
|
|
224
|
-
|
|
243
|
+
context.emitter.emit(arc_class.new(
|
|
244
|
+
x: cx, y: cy, radius: normalized_rx, angle1: theta1, angle2: theta2,
|
|
245
|
+
))
|
|
225
246
|
end
|
|
226
|
-
state.
|
|
247
|
+
state.line_to(x, y)
|
|
227
248
|
end
|
|
228
249
|
|
|
229
250
|
# ---- helpers ----
|
|
@@ -254,7 +275,8 @@ module Postsvg
|
|
|
254
275
|
def self.reflect_control(smooth, state)
|
|
255
276
|
return [state.current_x, state.current_y] unless smooth.smoothable
|
|
256
277
|
|
|
257
|
-
[reflect_x(smooth.control_x, state),
|
|
278
|
+
[reflect_x(smooth.control_x, state),
|
|
279
|
+
reflect_y(smooth.control_y, state)]
|
|
258
280
|
end
|
|
259
281
|
|
|
260
282
|
def self.reflect_control_rel(smooth, state)
|
|
@@ -263,11 +285,11 @@ module Postsvg
|
|
|
263
285
|
end
|
|
264
286
|
|
|
265
287
|
def self.reflect_x(control_x, state)
|
|
266
|
-
2 * state.current_x - control_x
|
|
288
|
+
(2 * state.current_x) - control_x
|
|
267
289
|
end
|
|
268
290
|
|
|
269
291
|
def self.reflect_y(control_y, state)
|
|
270
|
-
2 * state.current_y - control_y
|
|
292
|
+
(2 * state.current_y) - control_y
|
|
271
293
|
end
|
|
272
294
|
|
|
273
295
|
# Mutable path-tracking state shared across command handlers.
|
|
@@ -15,9 +15,11 @@ module Postsvg
|
|
|
15
15
|
points = element.points
|
|
16
16
|
points.each_slice(2).with_index do |(x, y), idx|
|
|
17
17
|
if idx.zero?
|
|
18
|
-
context.emitter.emit(Model::Operators::Path::Moveto.new(x: x,
|
|
18
|
+
context.emitter.emit(Model::Operators::Path::Moveto.new(x: x,
|
|
19
|
+
y: y))
|
|
19
20
|
else
|
|
20
|
-
context.emitter.emit(Model::Operators::Path::Lineto.new(x: x,
|
|
21
|
+
context.emitter.emit(Model::Operators::Path::Lineto.new(x: x,
|
|
22
|
+
y: y))
|
|
21
23
|
end
|
|
22
24
|
end
|
|
23
25
|
context.emitter.emit(Model::Operators::Path::Closepath.new)
|
|
@@ -9,17 +9,18 @@ module Postsvg
|
|
|
9
9
|
def self.call(element, context)
|
|
10
10
|
context.emitter.emit(Model::Operators::GraphicsState::Gsave.new)
|
|
11
11
|
emit_transform(element, context)
|
|
12
|
-
emit_stroke(element, context)
|
|
13
12
|
context.emitter.emit(Model::Operators::Path::Newpath.new)
|
|
14
13
|
points = element.points
|
|
15
14
|
points.each_slice(2).with_index do |(x, y), idx|
|
|
16
15
|
if idx.zero?
|
|
17
|
-
context.emitter.emit(Model::Operators::Path::Moveto.new(x: x,
|
|
16
|
+
context.emitter.emit(Model::Operators::Path::Moveto.new(x: x,
|
|
17
|
+
y: y))
|
|
18
18
|
else
|
|
19
|
-
context.emitter.emit(Model::Operators::Path::Lineto.new(x: x,
|
|
19
|
+
context.emitter.emit(Model::Operators::Path::Lineto.new(x: x,
|
|
20
|
+
y: y))
|
|
20
21
|
end
|
|
21
22
|
end
|
|
22
|
-
context
|
|
23
|
+
emit_paint(element, context)
|
|
23
24
|
context.emitter.emit(Model::Operators::GraphicsState::Grestore.new)
|
|
24
25
|
xs = points.each_slice(2).map(&:first)
|
|
25
26
|
ys = points.each_slice(2).map(&:last)
|
|
@@ -10,12 +10,17 @@ module Postsvg
|
|
|
10
10
|
context.emitter.emit(Model::Operators::GraphicsState::Gsave.new)
|
|
11
11
|
emit_transform(element, context)
|
|
12
12
|
context.emitter.emit(Model::Operators::Path::Newpath.new)
|
|
13
|
-
x
|
|
14
|
-
|
|
13
|
+
x = element.x
|
|
14
|
+
y = element.y
|
|
15
|
+
w = element.width
|
|
16
|
+
h = element.height
|
|
15
17
|
context.emitter.emit(Model::Operators::Path::Moveto.new(x: x, y: y))
|
|
16
|
-
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: w,
|
|
17
|
-
|
|
18
|
-
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx:
|
|
18
|
+
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: w,
|
|
19
|
+
dy: 0))
|
|
20
|
+
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: 0,
|
|
21
|
+
dy: h))
|
|
22
|
+
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: -w,
|
|
23
|
+
dy: 0))
|
|
19
24
|
context.emitter.emit(Model::Operators::Path::Closepath.new)
|
|
20
25
|
emit_paint(element, context)
|
|
21
26
|
context.emitter.emit(Model::Operators::GraphicsState::Grestore.new)
|
|
@@ -20,8 +20,8 @@ module Postsvg
|
|
|
20
20
|
def emit_paint(element, context)
|
|
21
21
|
fill_paint = element.fill
|
|
22
22
|
stroke_paint = element.stroke_paint
|
|
23
|
-
fill_on = fill_paint
|
|
24
|
-
stroke_on = stroke_paint
|
|
23
|
+
fill_on = fill_paint&.color?
|
|
24
|
+
stroke_on = stroke_paint&.color?
|
|
25
25
|
|
|
26
26
|
if fill_on && stroke_on
|
|
27
27
|
emit_fill(element, context)
|
|
@@ -52,26 +52,26 @@ module Postsvg
|
|
|
52
52
|
|
|
53
53
|
def emit_fill(element, context)
|
|
54
54
|
paint = element.fill
|
|
55
|
-
return unless paint
|
|
55
|
+
return unless paint&.color?
|
|
56
56
|
|
|
57
57
|
color = paint.value
|
|
58
58
|
if color.gray?
|
|
59
59
|
context.emitter.emit(Model::Operators::Color::Setgray.new(gray: color.gray_level))
|
|
60
60
|
else
|
|
61
61
|
context.emitter.emit(Model::Operators::Color::Setrgbcolor.new(
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
red: color.red / 255.0, green: color.green / 255.0, blue: color.blue / 255.0,
|
|
63
|
+
))
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def emit_stroke(element, context)
|
|
68
68
|
paint = element.stroke_paint
|
|
69
|
-
return unless paint
|
|
69
|
+
return unless paint&.color?
|
|
70
70
|
|
|
71
71
|
color = paint.value
|
|
72
72
|
context.emitter.emit(Model::Operators::Color::Setrgbcolor.new(
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
red: color.red / 255.0, green: color.green / 255.0, blue: color.blue / 255.0,
|
|
74
|
+
))
|
|
75
75
|
stroke = element.stroke
|
|
76
76
|
return unless stroke
|
|
77
77
|
|
|
@@ -92,8 +92,8 @@ module Postsvg
|
|
|
92
92
|
|
|
93
93
|
transform.matrices.each do |matrix|
|
|
94
94
|
context.emitter.emit(Model::Operators::Transformations::Concat.new(matrix: [
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f
|
|
96
|
+
]))
|
|
97
97
|
end
|
|
98
98
|
end
|
|
99
99
|
|
|
@@ -102,7 +102,7 @@ module Postsvg
|
|
|
102
102
|
ys = points.each_slice(2).map(&:last)
|
|
103
103
|
return if xs.empty?
|
|
104
104
|
|
|
105
|
-
context.expand_bbox!(
|
|
105
|
+
context.expand_bbox!(xs.min..xs.max, ys.min..ys.max)
|
|
106
106
|
end
|
|
107
107
|
end
|
|
108
108
|
end
|
|
@@ -16,10 +16,11 @@ module Postsvg
|
|
|
16
16
|
# Element base class guarantees the +children+ getter.
|
|
17
17
|
def self.descend(element, context)
|
|
18
18
|
element.children.each do |child|
|
|
19
|
-
Postsvg::Translation::PsRenderer.default_registry.translate(child,
|
|
19
|
+
Postsvg::Translation::PsRenderer.default_registry.translate(child,
|
|
20
|
+
context)
|
|
20
21
|
end
|
|
21
22
|
end
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
25
|
end
|
|
25
|
-
end
|
|
26
|
+
end
|
|
@@ -8,7 +8,9 @@ module Postsvg
|
|
|
8
8
|
# /<font-family> findfont
|
|
9
9
|
# <font-size> scalefont
|
|
10
10
|
# setfont
|
|
11
|
-
# <x> <y>
|
|
11
|
+
# <x> <y> translate
|
|
12
|
+
# 1 -1 scale
|
|
13
|
+
# 0 0 moveto
|
|
12
14
|
# (text) show
|
|
13
15
|
#
|
|
14
16
|
# Real font metrics (kerning, ligatures, glyph substitution) are
|
|
@@ -21,18 +23,25 @@ module Postsvg
|
|
|
21
23
|
def self.call(element, context)
|
|
22
24
|
context.emitter.emit(Model::Operators::GraphicsState::Gsave.new)
|
|
23
25
|
emit_transform(element, context)
|
|
24
|
-
emit_fill(element, context) if element.fill
|
|
26
|
+
emit_fill(element, context) if element.fill&.color?
|
|
25
27
|
|
|
26
28
|
font_name = font_name_value(element)
|
|
27
29
|
context.emitter.emit(Model::Operators::Font::Findfont.new(name: font_name))
|
|
28
30
|
context.emitter.emit(Model::Operators::Font::Scalefont.new(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
font: Model::Computed.new(operator_keyword: "findfont"),
|
|
32
|
+
size: element.font_size,
|
|
33
|
+
))
|
|
32
34
|
context.emitter.emit(Model::Operators::Font::Setfont.new(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
font: Model::Computed.new(operator_keyword: "scalefont"),
|
|
36
|
+
))
|
|
37
|
+
# The document transform converts SVG's downward y axis to PS.
|
|
38
|
+
# Restore upward font space around the baseline without undoing
|
|
39
|
+
# the authored text/group transforms or moving the anchor.
|
|
40
|
+
context.emitter.emit(Model::Operators::Transformations::Translate.new(
|
|
41
|
+
tx: element.x, ty: element.y,
|
|
42
|
+
))
|
|
43
|
+
context.emitter.emit(Model::Operators::Transformations::Scale.new(sx: 1, sy: -1))
|
|
44
|
+
context.emitter.emit(Model::Operators::Path::Moveto.new(x: 0, y: 0))
|
|
36
45
|
context.emitter.emit(Model::Operators::Font::Show.new(text: element.content.to_s))
|
|
37
46
|
|
|
38
47
|
context.emitter.emit(Model::Operators::GraphicsState::Grestore.new)
|
|
@@ -17,7 +17,8 @@ module Postsvg
|
|
|
17
17
|
autoload :TextHandler, "postsvg/translation/handlers/text_handler"
|
|
18
18
|
autoload :ImageHandler, "postsvg/translation/handlers/image_handler"
|
|
19
19
|
autoload :DefsHandler, "postsvg/translation/handlers/defs_handler"
|
|
20
|
-
autoload :ClipPathHandler,
|
|
20
|
+
autoload :ClipPathHandler,
|
|
21
|
+
"postsvg/translation/handlers/clip_path_handler"
|
|
21
22
|
autoload :OpenHandler, "postsvg/translation/handlers/open_handler"
|
|
22
23
|
autoload :Shared, "postsvg/translation/handlers/shared"
|
|
23
24
|
end
|
|
@@ -72,10 +72,12 @@ module Postsvg
|
|
|
72
72
|
|
|
73
73
|
def compute_viewport_bbox
|
|
74
74
|
if document.viewbox
|
|
75
|
-
|
|
76
|
-
BoundingBox.new(min_x:
|
|
75
|
+
x, y, width, height = document.viewbox
|
|
76
|
+
BoundingBox.new(min_x: x, min_y: y, max_x: x + width,
|
|
77
|
+
max_y: y + height)
|
|
77
78
|
elsif document.width && document.height
|
|
78
|
-
BoundingBox.new(min_x: 0, min_y: 0, max_x: document.width,
|
|
79
|
+
BoundingBox.new(min_x: 0, min_y: 0, max_x: document.width,
|
|
80
|
+
max_y: document.height)
|
|
79
81
|
else
|
|
80
82
|
BoundingBox.empty
|
|
81
83
|
end
|
|
@@ -85,6 +87,17 @@ module Postsvg
|
|
|
85
87
|
registry.translate(element, context)
|
|
86
88
|
end
|
|
87
89
|
|
|
90
|
+
# SVG's y axis points down; PostScript's points up. Convert once at
|
|
91
|
+
# the document boundary, before any authored element transforms.
|
|
92
|
+
def coordinate_conversion(bbox)
|
|
93
|
+
emitter = RecordEmitter.new
|
|
94
|
+
emitter.emit(Model::Operators::Transformations::Translate.new(
|
|
95
|
+
tx: 0, ty: bbox[3] - bbox[1],
|
|
96
|
+
))
|
|
97
|
+
emitter.emit(Model::Operators::Transformations::Scale.new(sx: 1, sy: -1))
|
|
98
|
+
emitter.records
|
|
99
|
+
end
|
|
100
|
+
|
|
88
101
|
def build_program(emitter, context)
|
|
89
102
|
bbox = context.bounding_box
|
|
90
103
|
header_bbox =
|
|
@@ -98,7 +111,8 @@ module Postsvg
|
|
|
98
111
|
epsf: eps,
|
|
99
112
|
title: "Translated by Postsvg #{::Postsvg::VERSION}",
|
|
100
113
|
)
|
|
101
|
-
Model::Program.new(header: header,
|
|
114
|
+
Model::Program.new(header: header,
|
|
115
|
+
body: coordinate_conversion(header_bbox) + emitter.records)
|
|
102
116
|
end
|
|
103
117
|
end
|
|
104
118
|
end
|