prawn-svg 0.21.0 → 0.22.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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -0
  3. data/README.md +11 -4
  4. data/lib/prawn-svg.rb +9 -6
  5. data/lib/prawn/svg/attributes.rb +6 -0
  6. data/lib/prawn/svg/attributes/clip_path.rb +17 -0
  7. data/lib/prawn/svg/attributes/display.rb +5 -0
  8. data/lib/prawn/svg/attributes/font.rb +38 -0
  9. data/lib/prawn/svg/attributes/opacity.rb +15 -0
  10. data/lib/prawn/svg/attributes/stroke.rb +35 -0
  11. data/lib/prawn/svg/attributes/transform.rb +50 -0
  12. data/lib/prawn/svg/calculators/aspect_ratio.rb +1 -1
  13. data/lib/prawn/svg/calculators/document_sizing.rb +2 -2
  14. data/lib/prawn/svg/calculators/pixels.rb +1 -1
  15. data/lib/prawn/svg/color.rb +44 -14
  16. data/lib/prawn/svg/document.rb +6 -5
  17. data/lib/prawn/svg/elements.rb +33 -0
  18. data/lib/prawn/svg/elements/base.rb +228 -0
  19. data/lib/prawn/svg/elements/circle.rb +25 -0
  20. data/lib/prawn/svg/elements/container.rb +15 -0
  21. data/lib/prawn/svg/elements/ellipse.rb +23 -0
  22. data/lib/prawn/svg/elements/gradient.rb +117 -0
  23. data/lib/prawn/svg/elements/ignored.rb +5 -0
  24. data/lib/prawn/svg/elements/image.rb +85 -0
  25. data/lib/prawn/svg/elements/line.rb +16 -0
  26. data/lib/prawn/svg/elements/path.rb +405 -0
  27. data/lib/prawn/svg/elements/polygon.rb +17 -0
  28. data/lib/prawn/svg/elements/polyline.rb +22 -0
  29. data/lib/prawn/svg/elements/rect.rb +33 -0
  30. data/lib/prawn/svg/elements/root.rb +9 -0
  31. data/lib/prawn/svg/elements/style.rb +10 -0
  32. data/lib/prawn/svg/elements/text.rb +87 -0
  33. data/lib/prawn/svg/elements/use.rb +29 -0
  34. data/lib/prawn/svg/extension.rb +2 -2
  35. data/lib/prawn/svg/font.rb +3 -3
  36. data/lib/prawn/svg/interface.rb +12 -5
  37. data/lib/prawn/svg/url_loader.rb +1 -1
  38. data/lib/prawn/svg/version.rb +2 -2
  39. data/prawn-svg.gemspec +3 -3
  40. data/spec/integration_spec.rb +59 -2
  41. data/spec/prawn/svg/attributes/font_spec.rb +49 -0
  42. data/spec/prawn/svg/attributes/transform_spec.rb +56 -0
  43. data/spec/prawn/svg/calculators/aspect_ratio_spec.rb +2 -2
  44. data/spec/prawn/svg/calculators/document_sizing_spec.rb +3 -3
  45. data/spec/prawn/svg/color_spec.rb +36 -15
  46. data/spec/prawn/svg/document_spec.rb +4 -4
  47. data/spec/prawn/svg/elements/base_spec.rb +125 -0
  48. data/spec/prawn/svg/elements/gradient_spec.rb +61 -0
  49. data/spec/prawn/svg/elements/path_spec.rb +123 -0
  50. data/spec/prawn/svg/elements/style_spec.rb +23 -0
  51. data/spec/prawn/svg/{parser → elements}/text_spec.rb +7 -8
  52. data/spec/prawn/svg/font_spec.rb +12 -12
  53. data/spec/prawn/svg/interface_spec.rb +7 -7
  54. data/spec/prawn/svg/url_loader_spec.rb +2 -2
  55. data/spec/sample_svg/gradients.svg +40 -0
  56. data/spec/sample_svg/rect02.svg +8 -11
  57. data/spec/spec_helper.rb +1 -1
  58. metadata +46 -18
  59. data/lib/prawn/svg/element.rb +0 -304
  60. data/lib/prawn/svg/parser.rb +0 -268
  61. data/lib/prawn/svg/parser/image.rb +0 -81
  62. data/lib/prawn/svg/parser/path.rb +0 -392
  63. data/lib/prawn/svg/parser/text.rb +0 -80
  64. data/spec/prawn/svg/element_spec.rb +0 -127
  65. data/spec/prawn/svg/parser/path_spec.rb +0 -89
  66. 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