postsvg 0.2.6 → 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/.rubocop_todo.yml +49 -533
- data/CLAUDE.md +3 -27
- data/Gemfile +2 -2
- data/README.adoc +4 -62
- data/Rakefile +0 -17
- data/lib/postsvg/cli.rb +5 -10
- data/lib/postsvg/color.rb +132 -0
- data/lib/postsvg/format_number.rb +22 -0
- data/lib/postsvg/graphics_context.rb +1 -2
- data/lib/postsvg/interpreter.rb +2 -1
- data/lib/postsvg/matrix.rb +106 -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 +1 -1
- data/lib/postsvg/parser/postscript_parser.rb +7 -7
- data/lib/postsvg/renderer.rb +3 -7
- 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/clip_path_registry.rb +1 -3
- data/lib/postsvg/svg/element.rb +2 -4
- data/lib/postsvg/svg/elements/ellipse.rb +1 -2
- data/lib/postsvg/svg/elements/svg.rb +1 -2
- data/lib/postsvg/svg/paint.rb +1 -1
- data/lib/postsvg/svg/parser.rb +3 -3
- data/lib/postsvg/svg/path_data/parser.rb +6 -10
- data/lib/postsvg/svg/stroke.rb +2 -4
- data/lib/postsvg/svg/transform_list.rb +1 -1
- data/lib/postsvg/svg.rb +1 -1
- data/lib/postsvg/svg_builder.rb +11 -15
- data/lib/postsvg/translation/arc_converter.rb +14 -23
- data/lib/postsvg/translation/bounding_box.rb +4 -7
- data/lib/postsvg/translation/handlers/circle_handler.rb +2 -2
- data/lib/postsvg/translation/handlers/ellipse_handler.rb +3 -8
- data/lib/postsvg/translation/handlers/group_handler.rb +1 -2
- data/lib/postsvg/translation/handlers/image_handler.rb +2 -3
- data/lib/postsvg/translation/handlers/line_handler.rb +2 -6
- data/lib/postsvg/translation/handlers/open_handler.rb +2 -3
- data/lib/postsvg/translation/handlers/path_handler.rb +60 -82
- data/lib/postsvg/translation/handlers/polygon_handler.rb +2 -4
- data/lib/postsvg/translation/handlers/polyline_handler.rb +4 -5
- data/lib/postsvg/translation/handlers/rect_handler.rb +5 -10
- data/lib/postsvg/translation/handlers/shared.rb +11 -11
- data/lib/postsvg/translation/handlers/svg_handler.rb +2 -3
- data/lib/postsvg/translation/handlers/text_handler.rb +8 -17
- data/lib/postsvg/translation/handlers.rb +1 -2
- data/lib/postsvg/translation/ps_renderer.rb +4 -18
- data/lib/postsvg/translation/record_emitter.rb +0 -54
- 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 +5 -8
- data/lib/postsvg/visitors/ps_visitor/common.rb +2 -1
- 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 +4 -8
- data/lib/postsvg/visitors/ps_visitor/font.rb +4 -15
- data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +5 -7
- data/lib/postsvg/visitors/ps_visitor/painting.rb +3 -3
- data/lib/postsvg/visitors/ps_visitor/path.rb +33 -52
- data/lib/postsvg/visitors/ps_visitor/transformations.rb +7 -45
- data/lib/postsvg/visitors/ps_visitor.rb +2 -18
- data/lib/postsvg.rb +38 -43
- metadata +39 -20
- data/gemfiles/native-parity/Gemfile +0 -8
|
@@ -11,7 +11,7 @@ module Postsvg
|
|
|
11
11
|
|
|
12
12
|
module_function
|
|
13
13
|
|
|
14
|
-
# rubocop:disable
|
|
14
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists
|
|
15
15
|
def endpoint_to_center(x1:, y1:, rx:, ry:, x_axis_rotation:,
|
|
16
16
|
large_arc:, sweep:, x2:, y2:)
|
|
17
17
|
rx = rx.abs
|
|
@@ -26,8 +26,8 @@ module Postsvg
|
|
|
26
26
|
# Step 1: rotate and scale (x1,y1) into the unit-circle frame.
|
|
27
27
|
dx = (x1 - x2) / 2.0
|
|
28
28
|
dy = (y1 - y2) / 2.0
|
|
29
|
-
x1p =
|
|
30
|
-
y1p =
|
|
29
|
+
x1p = cos_phi * dx + sin_phi * dy
|
|
30
|
+
y1p = -sin_phi * dx + cos_phi * dy
|
|
31
31
|
|
|
32
32
|
# Ensure radii are large enough; if not, scale them up.
|
|
33
33
|
rx_sq = rx * rx
|
|
@@ -44,17 +44,17 @@ module Postsvg
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# Step 2: compute center (cx', cy') in the rotated frame.
|
|
47
|
-
sign = large_arc == sweep ? -1 : 1
|
|
48
|
-
num =
|
|
49
|
-
den =
|
|
47
|
+
sign = (large_arc == sweep) ? -1 : 1
|
|
48
|
+
num = rx_sq * ry_sq - rx_sq * y1p_sq - ry_sq * x1p_sq
|
|
49
|
+
den = rx_sq * y1p_sq + ry_sq * x1p_sq
|
|
50
50
|
den = EPSILON if den.abs < EPSILON
|
|
51
51
|
coef = sign * Math.sqrt([num / den, 0.0].max)
|
|
52
52
|
cxp = coef * (rx * y1p) / ry
|
|
53
53
|
cyp = -coef * (ry * x1p) / rx
|
|
54
54
|
|
|
55
55
|
# Step 3: rotate (cx', cy') back into original frame.
|
|
56
|
-
cx =
|
|
57
|
-
cy =
|
|
56
|
+
cx = cos_phi * cxp - sin_phi * cyp + (x1 + x2) / 2.0
|
|
57
|
+
cy = sin_phi * cxp + cos_phi * cyp + (y1 + y2) / 2.0
|
|
58
58
|
|
|
59
59
|
# Step 4: compute angles.
|
|
60
60
|
ux = (x1p - cxp) / rx
|
|
@@ -70,25 +70,16 @@ module Postsvg
|
|
|
70
70
|
|
|
71
71
|
[cx, cy, rx, ry, phi, theta1 * 180.0 / Math::PI, theta2 * 180.0 / Math::PI]
|
|
72
72
|
end
|
|
73
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists
|
|
73
74
|
|
|
74
75
|
def angle_between(ux, uy, vx, vy)
|
|
75
|
-
|
|
76
|
+
dot = ux * vx + uy * vy
|
|
77
|
+
len = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy))
|
|
76
78
|
return 0.0 if len < EPSILON
|
|
77
79
|
|
|
78
|
-
cos_val = (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def vector_length(ux, uy, vx, vy)
|
|
83
|
-
Math.sqrt(((ux * ux) + (uy * uy)) * ((vx * vx) + (vy * vy)))
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def dot_product(ux, uy, vx, vy)
|
|
87
|
-
(ux * vx) + (uy * vy)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def cross_sign(ux, uy, vx, vy)
|
|
91
|
-
((ux * vy) - (uy * vx)).negative? ? -1.0 : 1.0
|
|
80
|
+
cos_val = (dot / len).clamp(-1.0, 1.0)
|
|
81
|
+
sign = (ux * vy - uy * vx).negative? ? -1.0 : 1.0
|
|
82
|
+
sign * Math.acos(cos_val)
|
|
92
83
|
end
|
|
93
84
|
end
|
|
94
85
|
end
|
|
@@ -27,14 +27,11 @@ module Postsvg
|
|
|
27
27
|
def expand(x_range, y_range)
|
|
28
28
|
return self if x_range.nil? || y_range.nil?
|
|
29
29
|
|
|
30
|
-
new_min_x = [(empty? ? x_range.begin : @min_x),
|
|
31
|
-
x_range.begin].compact.min
|
|
30
|
+
new_min_x = [(empty? ? x_range.begin : @min_x), x_range.begin].compact.min
|
|
32
31
|
new_max_x = [(empty? ? x_range.end : @max_x), x_range.end].compact.max
|
|
33
|
-
new_min_y = [(empty? ? y_range.begin : @min_y),
|
|
34
|
-
y_range.begin].compact.min
|
|
32
|
+
new_min_y = [(empty? ? y_range.begin : @min_y), y_range.begin].compact.min
|
|
35
33
|
new_max_y = [(empty? ? y_range.end : @max_y), y_range.end].compact.max
|
|
36
|
-
BoundingBox.new(min_x: new_min_x, min_y: new_min_y, max_x: new_max_x,
|
|
37
|
-
max_y: new_max_y)
|
|
34
|
+
BoundingBox.new(min_x: new_min_x, min_y: new_min_y, max_x: new_max_x, max_y: new_max_y)
|
|
38
35
|
end
|
|
39
36
|
|
|
40
37
|
def to_a
|
|
@@ -54,7 +51,7 @@ module Postsvg
|
|
|
54
51
|
|
|
55
52
|
"%s %s %s %s" % [
|
|
56
53
|
FormatNumber.call(@min_x), FormatNumber.call(@min_y),
|
|
57
|
-
FormatNumber.call(@max_x), FormatNumber.call(@max_y)
|
|
54
|
+
FormatNumber.call(@max_x), FormatNumber.call(@max_y),
|
|
58
55
|
]
|
|
59
56
|
end
|
|
60
57
|
end
|
|
@@ -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,14 +17,9 @@ 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
|
-
|
|
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))
|
|
20
|
+
context.emitter.emit(Model::Operators::Transformations::Translate.new(tx: cx, ty: cy))
|
|
21
|
+
context.emitter.emit(Model::Operators::Transformations::Scale.new(sx: scale, sy: 1))
|
|
22
|
+
context.emitter.emit(Model::Operators::Path::Arc.new(x: 0, y: 0, radius: ry, angle1: 0, angle2: 360))
|
|
28
23
|
end
|
|
29
24
|
emit_paint(element, context)
|
|
30
25
|
context.emitter.emit(Model::Operators::GraphicsState::Grestore.new)
|
|
@@ -11,8 +11,7 @@ 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,
|
|
15
|
-
context)
|
|
14
|
+
Postsvg::Translation::PsRenderer::DEFAULT_REGISTRY.translate(child, context)
|
|
16
15
|
end
|
|
17
16
|
context.emitter.emit(Model::Operators::GraphicsState::Grestore.new)
|
|
18
17
|
end
|
|
@@ -10,10 +10,9 @@ 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
|
-
element.href || "(none)"
|
|
13
|
+
href = 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,
|
|
16
|
-
element.y + element.height)
|
|
15
|
+
expand_bbox(context, element.x, element.y, element.x + element.width, element.y + element.height)
|
|
17
16
|
end
|
|
18
17
|
end
|
|
19
18
|
end
|
|
@@ -11,12 +11,8 @@ 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
|
-
|
|
16
|
-
))
|
|
17
|
-
context.emitter.emit(Model::Operators::Path::Lineto.new(
|
|
18
|
-
x: element.x2, y: element.y2,
|
|
19
|
-
))
|
|
14
|
+
context.emitter.emit(Model::Operators::Path::Moveto.new(x: element.x1, y: element.y1))
|
|
15
|
+
context.emitter.emit(Model::Operators::Path::Lineto.new(x: element.x2, y: element.y2))
|
|
20
16
|
context.emitter.emit(Model::Operators::Painting::Stroke.new)
|
|
21
17
|
context.emitter.emit(Model::Operators::GraphicsState::Grestore.new)
|
|
22
18
|
expand_bbox(context, element.x1, element.y1, element.x2, element.y2)
|
|
@@ -9,11 +9,10 @@ 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,
|
|
13
|
-
context)
|
|
12
|
+
Postsvg::Translation::PsRenderer.default_registry.translate(child, context)
|
|
14
13
|
end
|
|
15
14
|
end
|
|
16
15
|
end
|
|
17
16
|
end
|
|
18
17
|
end
|
|
19
|
-
end
|
|
18
|
+
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 vertical line
|
|
13
|
+
# - V/v horizontal 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,8 +22,7 @@ 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,
|
|
26
|
-
keyword_init: true)
|
|
25
|
+
SmoothState = Struct.new(:control_x, :control_y, :smoothable, keyword_init: true)
|
|
27
26
|
|
|
28
27
|
def self.call(element, context)
|
|
29
28
|
context.emitter.emit(Model::Operators::GraphicsState::Gsave.new)
|
|
@@ -31,8 +30,7 @@ module Postsvg
|
|
|
31
30
|
context.emitter.emit(Model::Operators::Path::Newpath.new)
|
|
32
31
|
|
|
33
32
|
state = PathState.new
|
|
34
|
-
smooth = SmoothState.new(control_x: nil, control_y: nil,
|
|
35
|
-
smoothable: false)
|
|
33
|
+
smooth = SmoothState.new(control_x: nil, control_y: nil, smoothable: false)
|
|
36
34
|
element.commands.each do |cmd|
|
|
37
35
|
dispatch_command(cmd, state, smooth, context)
|
|
38
36
|
end
|
|
@@ -50,7 +48,7 @@ module Postsvg
|
|
|
50
48
|
when "H", "h"
|
|
51
49
|
handle_hv(cmd, state, context, horizontal: cmd.opcode.upcase == "H")
|
|
52
50
|
when "V", "v"
|
|
53
|
-
handle_hv(cmd, state, context, horizontal:
|
|
51
|
+
handle_hv(cmd, state, context, horizontal: cmd.opcode.upcase == "V")
|
|
54
52
|
when "C", "c"
|
|
55
53
|
handle_cubic(cmd, state, smooth, context)
|
|
56
54
|
when "S", "s"
|
|
@@ -76,8 +74,7 @@ module Postsvg
|
|
|
76
74
|
context.emitter.emit(Model::Operators::Path::Moveto.new(x: x, y: y))
|
|
77
75
|
else
|
|
78
76
|
state.move_to_rel(x, y)
|
|
79
|
-
context.emitter.emit(Model::Operators::Path::Rmoveto.new(dx: x,
|
|
80
|
-
dy: y))
|
|
77
|
+
context.emitter.emit(Model::Operators::Path::Rmoveto.new(dx: x, dy: y))
|
|
81
78
|
end
|
|
82
79
|
smooth.smoothable = false
|
|
83
80
|
end
|
|
@@ -89,8 +86,7 @@ module Postsvg
|
|
|
89
86
|
context.emitter.emit(Model::Operators::Path::Lineto.new(x: x, y: y))
|
|
90
87
|
else
|
|
91
88
|
state.line_to_rel(x, y)
|
|
92
|
-
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: x,
|
|
93
|
-
dy: y))
|
|
89
|
+
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: x, dy: y))
|
|
94
90
|
end
|
|
95
91
|
end
|
|
96
92
|
|
|
@@ -100,23 +96,18 @@ module Postsvg
|
|
|
100
96
|
if horizontal
|
|
101
97
|
new_x = v
|
|
102
98
|
state.line_to(new_x, state.current_y)
|
|
103
|
-
context.emitter.emit(Model::Operators::Path::Lineto.new(x: new_x,
|
|
104
|
-
y: state.current_y))
|
|
99
|
+
context.emitter.emit(Model::Operators::Path::Lineto.new(x: new_x, y: state.current_y))
|
|
105
100
|
else
|
|
106
101
|
new_y = v
|
|
107
102
|
state.line_to(state.current_x, new_y)
|
|
108
|
-
context.emitter.emit(Model::Operators::Path::Lineto.new(
|
|
109
|
-
x: state.current_x, y: new_y,
|
|
110
|
-
))
|
|
103
|
+
context.emitter.emit(Model::Operators::Path::Lineto.new(x: state.current_x, y: new_y))
|
|
111
104
|
end
|
|
112
105
|
elsif horizontal
|
|
113
106
|
state.line_to_rel(v, 0)
|
|
114
|
-
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: v,
|
|
115
|
-
dy: 0))
|
|
107
|
+
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: v, dy: 0))
|
|
116
108
|
else
|
|
117
109
|
state.line_to_rel(0, v)
|
|
118
|
-
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: 0,
|
|
119
|
-
dy: v))
|
|
110
|
+
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: 0, dy: v))
|
|
120
111
|
end
|
|
121
112
|
end
|
|
122
113
|
|
|
@@ -125,8 +116,8 @@ module Postsvg
|
|
|
125
116
|
x1, y1, x2, y2, x3, y3 = adapt_curve_args(args, cmd.opcode, state)
|
|
126
117
|
state.curve_to(x3, y3)
|
|
127
118
|
context.emitter.emit(Model::Operators::Path::Curveto.new(
|
|
128
|
-
|
|
129
|
-
|
|
119
|
+
x1: x1, y1: y1, x2: x2, y2: y2, x3: x3, y3: y3,
|
|
120
|
+
))
|
|
130
121
|
smooth.control_x = x2
|
|
131
122
|
smooth.control_y = y2
|
|
132
123
|
smooth.smoothable = true
|
|
@@ -138,15 +129,14 @@ module Postsvg
|
|
|
138
129
|
x1, y1 = reflect_control(smooth, state)
|
|
139
130
|
state.curve_to(x3, y3)
|
|
140
131
|
context.emitter.emit(Model::Operators::Path::Curveto.new(
|
|
141
|
-
|
|
142
|
-
|
|
132
|
+
x1: x1, y1: y1, x2: x2, y2: y2, x3: x3, y3: y3,
|
|
133
|
+
))
|
|
143
134
|
else
|
|
144
135
|
dx1, dy1 = reflect_control_rel(smooth, state)
|
|
145
|
-
state.curve_to_rel(dx2: x2, dy2: y2, dx3: x3, dy3: y3, dx1: dx1,
|
|
146
|
-
dy1: dy1)
|
|
136
|
+
state.curve_to_rel(dx2: x2, dy2: y2, dx3: x3, dy3: y3, dx1: dx1, dy1: dy1)
|
|
147
137
|
context.emitter.emit(Model::Operators::Path::Rcurveto.new(
|
|
148
|
-
|
|
149
|
-
|
|
138
|
+
dx1: dx1, dy1: dy1, dx2: x2, dy2: y2, dx3: x3, dy3: y3,
|
|
139
|
+
))
|
|
150
140
|
x2 = state.current_x - x2
|
|
151
141
|
y2 = state.current_y - y2
|
|
152
142
|
end
|
|
@@ -160,16 +150,15 @@ module Postsvg
|
|
|
160
150
|
qx, qy, x3, y3 = adapt_quadratic_args(args, cmd.opcode, state)
|
|
161
151
|
# Convert quadratic to cubic: c1 = p0 + 2/3*(q-p0),
|
|
162
152
|
# c2 = p3 + 2/3*(q-p3).
|
|
163
|
-
p0x = state.current_x
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
c2y = y3 + ((2.0 / 3.0) * (qy - y3))
|
|
153
|
+
p0x, p0y = state.current_x, state.current_y
|
|
154
|
+
c1x = p0x + (2.0 / 3.0) * (qx - p0x)
|
|
155
|
+
c1y = p0y + (2.0 / 3.0) * (qy - p0y)
|
|
156
|
+
c2x = x3 + (2.0 / 3.0) * (qx - x3)
|
|
157
|
+
c2y = y3 + (2.0 / 3.0) * (qy - y3)
|
|
169
158
|
state.curve_to(x3, y3)
|
|
170
159
|
context.emitter.emit(Model::Operators::Path::Curveto.new(
|
|
171
|
-
|
|
172
|
-
|
|
160
|
+
x1: c1x, y1: c1y, x2: c2x, y2: c2y, x3: x3, y3: y3,
|
|
161
|
+
))
|
|
173
162
|
smooth.control_x = qx
|
|
174
163
|
smooth.control_y = qy
|
|
175
164
|
smooth.smoothable = true
|
|
@@ -177,27 +166,28 @@ module Postsvg
|
|
|
177
166
|
|
|
178
167
|
def self.handle_smooth_quadratic(cmd, state, smooth, context)
|
|
179
168
|
x3, y3 = cmd.args
|
|
180
|
-
if cmd.opcode == "t"
|
|
181
|
-
x3 += state.current_x
|
|
182
|
-
y3 += state.current_y
|
|
183
|
-
end
|
|
184
169
|
qx, qy =
|
|
185
170
|
if smooth.smoothable
|
|
186
|
-
[reflect_x(smooth.control_x, state),
|
|
187
|
-
reflect_y(smooth.control_y, state)]
|
|
171
|
+
[reflect_x(smooth.control_x, state), reflect_y(smooth.control_y, state)]
|
|
188
172
|
else
|
|
189
173
|
[state.current_x, state.current_y]
|
|
190
174
|
end
|
|
191
|
-
p0x = state.current_x
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
175
|
+
p0x, p0y = state.current_x, state.current_y
|
|
176
|
+
c1x = p0x + (2.0 / 3.0) * (qx - p0x)
|
|
177
|
+
c1y = p0y + (2.0 / 3.0) * (qy - p0y)
|
|
178
|
+
c2x = x3 + (2.0 / 3.0) * (qx - x3)
|
|
179
|
+
c2y = y3 + (2.0 / 3.0) * (qy - y3)
|
|
180
|
+
if cmd.opcode == "T"
|
|
181
|
+
state.curve_to(x3, y3)
|
|
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
|
|
198
188
|
context.emitter.emit(Model::Operators::Path::Curveto.new(
|
|
199
|
-
|
|
200
|
-
|
|
189
|
+
x1: c1x, y1: c1y, x2: c2x, y2: c2y, x3: state.current_x, y3: state.current_y,
|
|
190
|
+
))
|
|
201
191
|
smooth.control_x = qx
|
|
202
192
|
smooth.control_y = qy
|
|
203
193
|
smooth.smoothable = true
|
|
@@ -208,43 +198,32 @@ module Postsvg
|
|
|
208
198
|
# W3C-endorsed endpoint-to-center conversion.
|
|
209
199
|
def self.handle_arc(cmd, state, context)
|
|
210
200
|
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
|
-
|
|
218
201
|
cx, cy, normalized_rx, normalized_ry, psi, theta1, theta2 =
|
|
219
202
|
Postsvg::Translation::ArcConverter.endpoint_to_center(
|
|
220
203
|
x1: state.current_x, y1: state.current_y,
|
|
221
204
|
rx: rx, ry: ry, x_axis_rotation: x_axis_rotation,
|
|
222
205
|
large_arc: large_arc_flag != 0, sweep: sweep_flag != 0,
|
|
223
|
-
x2: x, y2: y
|
|
206
|
+
x2: x, y2: y,
|
|
224
207
|
)
|
|
225
|
-
#
|
|
226
|
-
|
|
208
|
+
# Emit arc with center, radius, angle range. For elliptical
|
|
209
|
+
# arcs (rx != ry or non-zero rotation), emit scale/rotate
|
|
210
|
+
# transforms around the arc.
|
|
227
211
|
if x_axis_rotation.abs > 1e-6 || (rx - ry).abs > 1e-6
|
|
228
|
-
context.emitter.emit(Model::Operators::
|
|
229
|
-
context.emitter.emit(Model::Operators::Transformations::Currentmatrix.new)
|
|
212
|
+
context.emitter.emit(Model::Operators::GraphicsState::Gsave.new)
|
|
230
213
|
context.emitter.emit(Model::Operators::Transformations::Concat.new(matrix: [
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
context.emitter.emit(Model::Operators::Transformations::Scale.new(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
))
|
|
239
|
-
context.emitter.emit(Model::Operators::Transformations::Setmatrix.new(
|
|
240
|
-
matrix: Model::Computed.new(operator_keyword: "currentmatrix"),
|
|
241
|
-
))
|
|
214
|
+
Math.cos(psi), Math.sin(psi), -Math.sin(psi), Math.cos(psi), cx, cy,
|
|
215
|
+
]))
|
|
216
|
+
context.emitter.emit(Model::Operators::Transformations::Scale.new(sx: normalized_rx, sy: normalized_ry))
|
|
217
|
+
context.emitter.emit(Model::Operators::Path::Arc.new(
|
|
218
|
+
x: 0, y: 0, radius: 1.0, angle1: theta1, angle2: theta2,
|
|
219
|
+
))
|
|
220
|
+
context.emitter.emit(Model::Operators::GraphicsState::Grestore.new)
|
|
242
221
|
else
|
|
243
|
-
context.emitter.emit(
|
|
244
|
-
|
|
245
|
-
|
|
222
|
+
context.emitter.emit(Model::Operators::Path::Arc.new(
|
|
223
|
+
x: cx, y: cy, radius: normalized_rx, angle1: theta1, angle2: theta2,
|
|
224
|
+
))
|
|
246
225
|
end
|
|
247
|
-
state.
|
|
226
|
+
state.move_to(x, y)
|
|
248
227
|
end
|
|
249
228
|
|
|
250
229
|
# ---- helpers ----
|
|
@@ -275,8 +254,7 @@ module Postsvg
|
|
|
275
254
|
def self.reflect_control(smooth, state)
|
|
276
255
|
return [state.current_x, state.current_y] unless smooth.smoothable
|
|
277
256
|
|
|
278
|
-
[reflect_x(smooth.control_x, state),
|
|
279
|
-
reflect_y(smooth.control_y, state)]
|
|
257
|
+
[reflect_x(smooth.control_x, state), reflect_y(smooth.control_y, state)]
|
|
280
258
|
end
|
|
281
259
|
|
|
282
260
|
def self.reflect_control_rel(smooth, state)
|
|
@@ -285,11 +263,11 @@ module Postsvg
|
|
|
285
263
|
end
|
|
286
264
|
|
|
287
265
|
def self.reflect_x(control_x, state)
|
|
288
|
-
|
|
266
|
+
2 * state.current_x - control_x
|
|
289
267
|
end
|
|
290
268
|
|
|
291
269
|
def self.reflect_y(control_y, state)
|
|
292
|
-
|
|
270
|
+
2 * state.current_y - control_y
|
|
293
271
|
end
|
|
294
272
|
|
|
295
273
|
# Mutable path-tracking state shared across command handlers.
|
|
@@ -15,11 +15,9 @@ 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,
|
|
19
|
-
y: y))
|
|
18
|
+
context.emitter.emit(Model::Operators::Path::Moveto.new(x: x, y: y))
|
|
20
19
|
else
|
|
21
|
-
context.emitter.emit(Model::Operators::Path::Lineto.new(x: x,
|
|
22
|
-
y: y))
|
|
20
|
+
context.emitter.emit(Model::Operators::Path::Lineto.new(x: x, y: y))
|
|
23
21
|
end
|
|
24
22
|
end
|
|
25
23
|
context.emitter.emit(Model::Operators::Path::Closepath.new)
|
|
@@ -9,18 +9,17 @@ 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)
|
|
12
13
|
context.emitter.emit(Model::Operators::Path::Newpath.new)
|
|
13
14
|
points = element.points
|
|
14
15
|
points.each_slice(2).with_index do |(x, y), idx|
|
|
15
16
|
if idx.zero?
|
|
16
|
-
context.emitter.emit(Model::Operators::Path::Moveto.new(x: x,
|
|
17
|
-
y: y))
|
|
17
|
+
context.emitter.emit(Model::Operators::Path::Moveto.new(x: x, y: y))
|
|
18
18
|
else
|
|
19
|
-
context.emitter.emit(Model::Operators::Path::Lineto.new(x: x,
|
|
20
|
-
y: y))
|
|
19
|
+
context.emitter.emit(Model::Operators::Path::Lineto.new(x: x, y: y))
|
|
21
20
|
end
|
|
22
21
|
end
|
|
23
|
-
|
|
22
|
+
context.emitter.emit(Model::Operators::Painting::Stroke.new)
|
|
24
23
|
context.emitter.emit(Model::Operators::GraphicsState::Grestore.new)
|
|
25
24
|
xs = points.each_slice(2).map(&:first)
|
|
26
25
|
ys = points.each_slice(2).map(&:last)
|
|
@@ -10,17 +10,12 @@ 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 = element.x
|
|
14
|
-
|
|
15
|
-
w = element.width
|
|
16
|
-
h = element.height
|
|
13
|
+
x, y = element.x, element.y
|
|
14
|
+
w, h = element.width, element.height
|
|
17
15
|
context.emitter.emit(Model::Operators::Path::Moveto.new(x: x, y: y))
|
|
18
|
-
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: w,
|
|
19
|
-
|
|
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))
|
|
16
|
+
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: w, dy: 0))
|
|
17
|
+
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: 0, dy: h))
|
|
18
|
+
context.emitter.emit(Model::Operators::Path::Rlineto.new(dx: -w, dy: 0))
|
|
24
19
|
context.emitter.emit(Model::Operators::Path::Closepath.new)
|
|
25
20
|
emit_paint(element, context)
|
|
26
21
|
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 && fill_paint.color?
|
|
24
|
+
stroke_on = stroke_paint && 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 && 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 && 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!(xs.min..xs.max, ys.min..ys.max)
|
|
105
|
+
context.expand_bbox!((xs.min..xs.max), (ys.min..ys.max))
|
|
106
106
|
end
|
|
107
107
|
end
|
|
108
108
|
end
|
|
@@ -16,11 +16,10 @@ 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,
|
|
20
|
-
context)
|
|
19
|
+
Postsvg::Translation::PsRenderer.default_registry.translate(child, context)
|
|
21
20
|
end
|
|
22
21
|
end
|
|
23
22
|
end
|
|
24
23
|
end
|
|
25
24
|
end
|
|
26
|
-
end
|
|
25
|
+
end
|
|
@@ -8,9 +8,7 @@ module Postsvg
|
|
|
8
8
|
# /<font-family> findfont
|
|
9
9
|
# <font-size> scalefont
|
|
10
10
|
# setfont
|
|
11
|
-
# <x> <y>
|
|
12
|
-
# 1 -1 scale
|
|
13
|
-
# 0 0 moveto
|
|
11
|
+
# <x> <y> moveto
|
|
14
12
|
# (text) show
|
|
15
13
|
#
|
|
16
14
|
# Real font metrics (kerning, ligatures, glyph substitution) are
|
|
@@ -23,25 +21,18 @@ module Postsvg
|
|
|
23
21
|
def self.call(element, context)
|
|
24
22
|
context.emitter.emit(Model::Operators::GraphicsState::Gsave.new)
|
|
25
23
|
emit_transform(element, context)
|
|
26
|
-
emit_fill(element, context) if element.fill
|
|
24
|
+
emit_fill(element, context) if element.fill && element.fill.color?
|
|
27
25
|
|
|
28
26
|
font_name = font_name_value(element)
|
|
29
27
|
context.emitter.emit(Model::Operators::Font::Findfont.new(name: font_name))
|
|
30
28
|
context.emitter.emit(Model::Operators::Font::Scalefont.new(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
font: Model::Computed.new(operator_keyword: "findfont"),
|
|
30
|
+
size: element.font_size,
|
|
31
|
+
))
|
|
34
32
|
context.emitter.emit(Model::Operators::Font::Setfont.new(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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))
|
|
33
|
+
font: Model::Computed.new(operator_keyword: "scalefont"),
|
|
34
|
+
))
|
|
35
|
+
context.emitter.emit(Model::Operators::Path::Moveto.new(x: element.x, y: element.y))
|
|
45
36
|
context.emitter.emit(Model::Operators::Font::Show.new(text: element.content.to_s))
|
|
46
37
|
|
|
47
38
|
context.emitter.emit(Model::Operators::GraphicsState::Grestore.new)
|