prawn-svg 0.21.0 → 0.22.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -0
- data/README.md +11 -4
- data/lib/prawn-svg.rb +9 -6
- data/lib/prawn/svg/attributes.rb +6 -0
- data/lib/prawn/svg/attributes/clip_path.rb +17 -0
- data/lib/prawn/svg/attributes/display.rb +5 -0
- data/lib/prawn/svg/attributes/font.rb +38 -0
- data/lib/prawn/svg/attributes/opacity.rb +15 -0
- data/lib/prawn/svg/attributes/stroke.rb +35 -0
- data/lib/prawn/svg/attributes/transform.rb +50 -0
- data/lib/prawn/svg/calculators/aspect_ratio.rb +1 -1
- data/lib/prawn/svg/calculators/document_sizing.rb +2 -2
- data/lib/prawn/svg/calculators/pixels.rb +1 -1
- data/lib/prawn/svg/color.rb +44 -14
- data/lib/prawn/svg/document.rb +6 -5
- data/lib/prawn/svg/elements.rb +33 -0
- data/lib/prawn/svg/elements/base.rb +228 -0
- data/lib/prawn/svg/elements/circle.rb +25 -0
- data/lib/prawn/svg/elements/container.rb +15 -0
- data/lib/prawn/svg/elements/ellipse.rb +23 -0
- data/lib/prawn/svg/elements/gradient.rb +117 -0
- data/lib/prawn/svg/elements/ignored.rb +5 -0
- data/lib/prawn/svg/elements/image.rb +85 -0
- data/lib/prawn/svg/elements/line.rb +16 -0
- data/lib/prawn/svg/elements/path.rb +405 -0
- data/lib/prawn/svg/elements/polygon.rb +17 -0
- data/lib/prawn/svg/elements/polyline.rb +22 -0
- data/lib/prawn/svg/elements/rect.rb +33 -0
- data/lib/prawn/svg/elements/root.rb +9 -0
- data/lib/prawn/svg/elements/style.rb +10 -0
- data/lib/prawn/svg/elements/text.rb +87 -0
- data/lib/prawn/svg/elements/use.rb +29 -0
- data/lib/prawn/svg/extension.rb +2 -2
- data/lib/prawn/svg/font.rb +3 -3
- data/lib/prawn/svg/interface.rb +12 -5
- data/lib/prawn/svg/url_loader.rb +1 -1
- data/lib/prawn/svg/version.rb +2 -2
- data/prawn-svg.gemspec +3 -3
- data/spec/integration_spec.rb +59 -2
- data/spec/prawn/svg/attributes/font_spec.rb +49 -0
- data/spec/prawn/svg/attributes/transform_spec.rb +56 -0
- data/spec/prawn/svg/calculators/aspect_ratio_spec.rb +2 -2
- data/spec/prawn/svg/calculators/document_sizing_spec.rb +3 -3
- data/spec/prawn/svg/color_spec.rb +36 -15
- data/spec/prawn/svg/document_spec.rb +4 -4
- data/spec/prawn/svg/elements/base_spec.rb +125 -0
- data/spec/prawn/svg/elements/gradient_spec.rb +61 -0
- data/spec/prawn/svg/elements/path_spec.rb +123 -0
- data/spec/prawn/svg/elements/style_spec.rb +23 -0
- data/spec/prawn/svg/{parser → elements}/text_spec.rb +7 -8
- data/spec/prawn/svg/font_spec.rb +12 -12
- data/spec/prawn/svg/interface_spec.rb +7 -7
- data/spec/prawn/svg/url_loader_spec.rb +2 -2
- data/spec/sample_svg/gradients.svg +40 -0
- data/spec/sample_svg/rect02.svg +8 -11
- data/spec/spec_helper.rb +1 -1
- metadata +46 -18
- data/lib/prawn/svg/element.rb +0 -304
- data/lib/prawn/svg/parser.rb +0 -268
- data/lib/prawn/svg/parser/image.rb +0 -81
- data/lib/prawn/svg/parser/path.rb +0 -392
- data/lib/prawn/svg/parser/text.rb +0 -80
- data/spec/prawn/svg/element_spec.rb +0 -127
- data/spec/prawn/svg/parser/path_spec.rb +0 -89
- data/spec/prawn/svg/parser_spec.rb +0 -55
@@ -1,80 +0,0 @@
|
|
1
|
-
class Prawn::Svg::Parser::Text
|
2
|
-
def parse(element)
|
3
|
-
element.add_call_and_enter "text_group"
|
4
|
-
internal_parse(element, [element.document.x(0)], [element.document.y(0)])
|
5
|
-
end
|
6
|
-
|
7
|
-
protected
|
8
|
-
def internal_parse(element, x_positions, y_positions, relative = false, preserve_space = false)
|
9
|
-
return if element.state[:display] == "none"
|
10
|
-
|
11
|
-
attrs = element.attributes
|
12
|
-
|
13
|
-
if attrs['x'] || attrs['y']
|
14
|
-
relative = false
|
15
|
-
x_positions = attrs['x'].split(/[\s,]+/).collect {|n| element.document.x(n)} if attrs['x']
|
16
|
-
y_positions = attrs['y'].split(/[\s,]+/).collect {|n| element.document.y(n)} if attrs['y']
|
17
|
-
end
|
18
|
-
|
19
|
-
if attrs['dx'] || attrs['dy']
|
20
|
-
element.add_call_and_enter "translate", element.document.distance(attrs['dx'] || 0), -element.document.distance(attrs['dy'] || 0)
|
21
|
-
end
|
22
|
-
|
23
|
-
case attrs['xml:space']
|
24
|
-
when 'preserve'
|
25
|
-
preserve_space = true
|
26
|
-
when 'default'
|
27
|
-
preserve_space = false
|
28
|
-
end
|
29
|
-
|
30
|
-
opts = {}
|
31
|
-
if size = element.state[:font_size]
|
32
|
-
opts[:size] = size
|
33
|
-
end
|
34
|
-
opts[:style] = element.state[:font_subfamily]
|
35
|
-
|
36
|
-
# This is not a prawn option but we can't work out how to render it here -
|
37
|
-
# it's handled by Svg#rewrite_call_arguments
|
38
|
-
if (anchor = attrs['text-anchor'] || element.state[:text_anchor]) &&
|
39
|
-
['start', 'middle', 'end'].include?(anchor)
|
40
|
-
opts[:text_anchor] = anchor
|
41
|
-
end
|
42
|
-
|
43
|
-
if spacing = attrs['letter-spacing']
|
44
|
-
element.add_call_and_enter 'character_spacing', element.document.points(spacing)
|
45
|
-
end
|
46
|
-
|
47
|
-
element.element.children.each do |child|
|
48
|
-
if child.node_type == :text
|
49
|
-
text = child.value.strip.gsub(preserve_space ? /[\n\t]/ : /\s+/, " ")
|
50
|
-
|
51
|
-
while text != ""
|
52
|
-
opts[:at] = [x_positions.first, y_positions.first]
|
53
|
-
|
54
|
-
if x_positions.length > 1 || y_positions.length > 1
|
55
|
-
element.add_call 'draw_text', text[0..0], opts.dup
|
56
|
-
text = text[1..-1]
|
57
|
-
|
58
|
-
x_positions.shift if x_positions.length > 1
|
59
|
-
y_positions.shift if y_positions.length > 1
|
60
|
-
else
|
61
|
-
element.add_call relative ? 'relative_draw_text' : 'draw_text', text, opts.dup
|
62
|
-
relative = true
|
63
|
-
break
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
elsif child.name == "tspan"
|
68
|
-
element.add_call 'save'
|
69
|
-
child.attributes['text-anchor'] ||= opts[:text_anchor] if opts[:text_anchor]
|
70
|
-
child_element = Prawn::Svg::Element.new(element.document, child, element.calls, element.state.dup)
|
71
|
-
internal_parse(child_element, x_positions, y_positions, relative, preserve_space)
|
72
|
-
child_element.append_calls_to_parent
|
73
|
-
element.add_call 'restore'
|
74
|
-
|
75
|
-
else
|
76
|
-
element.warnings << "Unknown tag '#{child.name}' inside text tag; ignoring"
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
@@ -1,127 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
-
|
3
|
-
describe Prawn::Svg::Element do
|
4
|
-
let(:document) { double(css_parser: nil) }
|
5
|
-
let(:e) { double(:attributes => {}, :name => "path") }
|
6
|
-
let(:element) { Prawn::Svg::Element.new(document, e, [], {}) }
|
7
|
-
|
8
|
-
describe "#parse_font_attributes_and_call" do
|
9
|
-
before do
|
10
|
-
@document = Struct.new(:fallback_font_name, :css_parser, :warnings).new("Courier", nil, [])
|
11
|
-
@element = Prawn::Svg::Element.new(@document, e, [], {})
|
12
|
-
end
|
13
|
-
|
14
|
-
it "uses a font if it can find it" do
|
15
|
-
@element.should_receive(:add_call_and_enter).with('font', 'Helvetica', :style => :normal)
|
16
|
-
|
17
|
-
@element.attributes["font-family"] = "Helvetica"
|
18
|
-
@element.send :parse_font_attributes_and_call
|
19
|
-
end
|
20
|
-
|
21
|
-
it "uses the fallback font if the requested font is not defined" do
|
22
|
-
@element.should_receive(:add_call_and_enter).with('font', 'Courier', :style => :normal)
|
23
|
-
|
24
|
-
@element.attributes["font-family"] = "Font That Doesn't Exist"
|
25
|
-
@element.send :parse_font_attributes_and_call
|
26
|
-
end
|
27
|
-
|
28
|
-
it "doesn't call the font method if there's no fallback font" do
|
29
|
-
@document.fallback_font_name = nil
|
30
|
-
|
31
|
-
@element.should_not_receive(:add_call_and_enter)
|
32
|
-
|
33
|
-
@element.attributes["font-family"] = "Font That Doesn't Exist"
|
34
|
-
@element.send :parse_font_attributes_and_call
|
35
|
-
@document.warnings.length.should == 1
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
describe "#parse_fill_and_stroke_attributes_and_call" do
|
40
|
-
subject { element.send :parse_fill_and_stroke_attributes_and_call }
|
41
|
-
|
42
|
-
it "doesn't change anything if no fill attribute provided" do
|
43
|
-
subject
|
44
|
-
expect(element.state[:fill]).to be nil
|
45
|
-
end
|
46
|
-
|
47
|
-
it "doesn't change anything if 'inherit' fill attribute provided" do
|
48
|
-
element.attributes['fill'] = 'inherit'
|
49
|
-
subject
|
50
|
-
expect(element.state[:fill]).to be nil
|
51
|
-
end
|
52
|
-
|
53
|
-
it "turns off filling if 'none' fill attribute provided" do
|
54
|
-
element.attributes['fill'] = 'none'
|
55
|
-
subject
|
56
|
-
expect(element.state[:fill]).to be false
|
57
|
-
end
|
58
|
-
|
59
|
-
it "uses the fill attribute's color" do
|
60
|
-
expect(element).to receive(:add_call).with('fill_color', 'ff0000')
|
61
|
-
element.attributes['fill'] = 'red'
|
62
|
-
subject
|
63
|
-
expect(element.state[:fill]).to be true
|
64
|
-
end
|
65
|
-
|
66
|
-
it "uses black if the fill attribute's color is unparseable" do
|
67
|
-
expect(element).to receive(:add_call).with('fill_color', '000000')
|
68
|
-
element.attributes['fill'] = 'blarble'
|
69
|
-
subject
|
70
|
-
expect(element.state[:fill]).to be true
|
71
|
-
end
|
72
|
-
|
73
|
-
it "uses the color attribute if 'currentColor' fill attribute provided" do
|
74
|
-
expect(element).to receive(:add_call).with('fill_color', 'ff0000')
|
75
|
-
element.attributes['fill'] = 'currentColor'
|
76
|
-
element.attributes['color'] = 'red'
|
77
|
-
subject
|
78
|
-
expect(element.state[:fill]).to be true
|
79
|
-
end
|
80
|
-
|
81
|
-
it "turns off filling if UnresolvableURLWithNoFallbackError is raised" do
|
82
|
-
element.attributes['fill'] = 'url()'
|
83
|
-
subject
|
84
|
-
expect(element.state[:fill]).to be false
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
describe "#parse_transform_attribute_and_call" do
|
89
|
-
subject { element.send :parse_transform_attribute_and_call }
|
90
|
-
|
91
|
-
describe "translate" do
|
92
|
-
it "handles a missing y argument" do
|
93
|
-
expect(element).to receive(:add_call_and_enter).with('translate', -5.5, 0)
|
94
|
-
expect(document).to receive(:distance).with(-5.5, :x).and_return(-5.5)
|
95
|
-
expect(document).to receive(:distance).with(0.0, :y).and_return(0.0)
|
96
|
-
|
97
|
-
element.attributes['transform'] = 'translate(-5.5)'
|
98
|
-
subject
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
describe "rotate" do
|
103
|
-
it "handles a single angle argument" do
|
104
|
-
expect(element).to receive(:add_call_and_enter).with('rotate', -5.5, :origin => [0, 0])
|
105
|
-
expect(document).to receive(:y).with('0').and_return(0)
|
106
|
-
|
107
|
-
element.attributes['transform'] = 'rotate(5.5)'
|
108
|
-
subject
|
109
|
-
end
|
110
|
-
|
111
|
-
it "handles three arguments" do
|
112
|
-
expect(element).to receive(:add_call_and_enter).with('rotate', -5.5, :origin => [1.0, 2.0])
|
113
|
-
expect(document).to receive(:x).with(1.0).and_return(1.0)
|
114
|
-
expect(document).to receive(:y).with(2.0).and_return(2.0)
|
115
|
-
|
116
|
-
element.attributes['transform'] = 'rotate(5.5 1 2)'
|
117
|
-
subject
|
118
|
-
end
|
119
|
-
|
120
|
-
it "does nothing and warns if two arguments" do
|
121
|
-
expect(document).to receive(:warnings).and_return([])
|
122
|
-
element.attributes['transform'] = 'rotate(5.5 1)'
|
123
|
-
subject
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
@@ -1,89 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Prawn::Svg::Parser::Path do
|
4
|
-
let(:path) { Prawn::Svg::Parser::Path.new }
|
5
|
-
|
6
|
-
describe "command parsing" do
|
7
|
-
it "correctly parses a valid path" do
|
8
|
-
calls = []
|
9
|
-
path.stub(:run_path_command) {|*args| calls << args}
|
10
|
-
path.parse("A12.34 -56.78 89B4 5 12-34 -.5.7+3 2.3e3 4e4 4e+4 c31,-2e-5C 6,7 T QX 0 Z")
|
11
|
-
|
12
|
-
calls.should == [
|
13
|
-
["A", [12.34, -56.78, 89]],
|
14
|
-
["B", [4, 5, 12, -34, -0.5, 0.7, 3, 2.3e3, 4e4, 4e4]],
|
15
|
-
["c", [31, -2e-5]],
|
16
|
-
["C", [6, 7]],
|
17
|
-
["T", []],
|
18
|
-
["Q", []],
|
19
|
-
["X", [0]],
|
20
|
-
["Z", []]
|
21
|
-
]
|
22
|
-
end
|
23
|
-
|
24
|
-
it "treats subsequent points to m/M command as relative/absolute depending on command" do
|
25
|
-
[
|
26
|
-
["M", [1,2,3,4]],
|
27
|
-
["L", [3,4]],
|
28
|
-
["m", [5,6,7,8]],
|
29
|
-
["l", [7,8]]
|
30
|
-
].each do |args|
|
31
|
-
path.should_receive(:run_path_command).with(*args).and_call_original
|
32
|
-
end
|
33
|
-
|
34
|
-
path.parse("M 1,2 3,4 m 5,6 7,8")
|
35
|
-
end
|
36
|
-
|
37
|
-
it "correctly parses an empty path" do
|
38
|
-
path.should_not_receive(:run_path_command)
|
39
|
-
path.parse("").should == []
|
40
|
-
path.parse(" ").should == []
|
41
|
-
end
|
42
|
-
|
43
|
-
it "raises on invalid characters in the path" do
|
44
|
-
lambda {path.parse("M 10 % 20")}.should raise_error(Prawn::Svg::Parser::Path::InvalidError)
|
45
|
-
end
|
46
|
-
|
47
|
-
it "raises on numerical data before a command letter" do
|
48
|
-
lambda {path.parse("10 P")}.should raise_error(Prawn::Svg::Parser::Path::InvalidError)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
context "when given an A path" do
|
53
|
-
it "uses bezier curves to approximate an arc path" do
|
54
|
-
result = path.parse("M 100 200 A 10 10 0 0 1 200 200")
|
55
|
-
|
56
|
-
expect(result).to eq [
|
57
|
-
["move_to", [100.0, 200.0]],
|
58
|
-
["curve_to", [150.0, 150.0, 100.0, 172.57081148225683, 122.57081148225683, 150.0]],
|
59
|
-
["curve_to", [200.0, 200.0, 177.42918851774317, 150.0, 200.0, 172.57081148225683]]
|
60
|
-
]
|
61
|
-
end
|
62
|
-
|
63
|
-
it "ignores a path that has an identical start and end point" do
|
64
|
-
result = path.parse("M 100 200 A 30 30 0 0 1 100 200")
|
65
|
-
|
66
|
-
expect(result).to eq [
|
67
|
-
["move_to", [100.0, 200.0]]
|
68
|
-
]
|
69
|
-
end
|
70
|
-
|
71
|
-
it "substitutes a line_to when rx is 0" do
|
72
|
-
result = path.parse("M 100 200 A 0 10 0 0 1 200 200")
|
73
|
-
|
74
|
-
expect(result).to eq [
|
75
|
-
["move_to", [100.0, 200.0]],
|
76
|
-
["line_to", [200.0, 200.0]]
|
77
|
-
]
|
78
|
-
end
|
79
|
-
|
80
|
-
it "substitutes a line_to when ry is 0" do
|
81
|
-
result = path.parse("M 100 200 A 10 0 0 0 1 200 200")
|
82
|
-
|
83
|
-
expect(result).to eq [
|
84
|
-
["move_to", [100.0, 200.0]],
|
85
|
-
["line_to", [200.0, 200.0]]
|
86
|
-
]
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Prawn::Svg::Parser do
|
4
|
-
describe "document width and height" do
|
5
|
-
it "handles the width and height being set as a %" do
|
6
|
-
svg = <<-SVG
|
7
|
-
<svg width="50%" height="50%" version="1.1">
|
8
|
-
<line x1="10%" y1="10%" x2="90%" y2="90%" />
|
9
|
-
</svg>
|
10
|
-
SVG
|
11
|
-
|
12
|
-
document = Prawn::Svg::Document.new(svg, [2000, 2000], {})
|
13
|
-
Prawn::Svg::Parser.new(document).parse[-2][-1].should == [["line", [100.0, 900.0, 900.0, 100.0], []]]
|
14
|
-
end
|
15
|
-
|
16
|
-
it "handles the width and height being set in inches" do
|
17
|
-
svg = <<-SVG
|
18
|
-
<svg width="10in" height="10in" version="1.1">
|
19
|
-
<line x1="1in" y1="1in" x2="9in" y2="9in" />
|
20
|
-
</svg>
|
21
|
-
SVG
|
22
|
-
|
23
|
-
document = Prawn::Svg::Document.new(svg, [2000, 2000], {})
|
24
|
-
Prawn::Svg::Parser.new(document).parse[-2][-1].should == [["line", [72.0, 720.0 - 72.0, 720.0 - 72.0, 72.0], []]]
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
describe :parse_element do
|
29
|
-
before(:each) do
|
30
|
-
@document = Prawn::Svg::Document.new("<svg></svg>", [100, 100], {})
|
31
|
-
@parser = Prawn::Svg::Parser.new(@document)
|
32
|
-
end
|
33
|
-
|
34
|
-
def mock_element(name, attributes = {})
|
35
|
-
e = double(:name => name, :attributes => attributes)
|
36
|
-
Prawn::Svg::Element.new(@document, e, [], {})
|
37
|
-
end
|
38
|
-
|
39
|
-
it "ignores tags it doesn't know about" do
|
40
|
-
calls = []
|
41
|
-
@parser.send :parse_element, mock_element("unknown")
|
42
|
-
calls.should == []
|
43
|
-
@document.warnings.length.should == 1
|
44
|
-
@document.warnings.first.should include("Unknown tag")
|
45
|
-
end
|
46
|
-
|
47
|
-
it "ignores tags that don't have all required attributes set" do
|
48
|
-
calls = []
|
49
|
-
@parser.send :parse_element, mock_element("ellipse", "rx" => "1")
|
50
|
-
calls.should == []
|
51
|
-
@document.warnings.length.should == 1
|
52
|
-
@document.warnings.first.should include("Must have attributes ry on tag ellipse")
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|