prawn-svg 0.9.1.10 → 0.9.1.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.gitignore +5 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +14 -0
  4. data/Rakefile +26 -0
  5. data/lib/prawn/svg/element.rb +7 -2
  6. data/lib/prawn/svg/font.rb +7 -4
  7. data/lib/prawn/svg/parser.rb +12 -4
  8. data/lib/prawn/svg/version.rb +5 -0
  9. data/prawn-svg.gemspec +23 -0
  10. data/spec/lib/parser_spec.rb +58 -0
  11. data/spec/lib/path_spec.rb +38 -0
  12. data/spec/lib/svg_spec.rb +22 -0
  13. data/spec/prawn/svg/document_spec.rb +27 -0
  14. data/spec/prawn/svg/element_spec.rb +32 -0
  15. data/spec/prawn/svg/font_spec.rb +31 -0
  16. data/spec/prawn/svg/parser/text_spec.rb +4 -0
  17. data/spec/sample_output/directory +0 -0
  18. data/spec/sample_svg/arcs01.svg +21 -0
  19. data/spec/sample_svg/circle01.svg +12 -0
  20. data/spec/sample_svg/cubic01.svg +37 -0
  21. data/spec/sample_svg/cubic01a.svg +40 -0
  22. data/spec/sample_svg/cubic02.svg +86 -0
  23. data/spec/sample_svg/ellipse01.svg +17 -0
  24. data/spec/sample_svg/line01.svg +22 -0
  25. data/spec/sample_svg/maths.svg +34 -0
  26. data/spec/sample_svg/omnigraffle.svg +41 -0
  27. data/spec/sample_svg/opacity01.svg +42 -0
  28. data/spec/sample_svg/polygon01.svg +17 -0
  29. data/spec/sample_svg/polyline01.svg +18 -0
  30. data/spec/sample_svg/quad01.svg +28 -0
  31. data/spec/sample_svg/rect01.svg +12 -0
  32. data/spec/sample_svg/rect02.svg +16 -0
  33. data/spec/sample_svg/rotate_scale.svg +38 -0
  34. data/spec/sample_svg/scruffy_graph.svg +134 -0
  35. data/spec/sample_svg/triangle01.svg +12 -0
  36. data/spec/sample_svg/tspan01.svg +17 -0
  37. data/spec/sample_svg/tspan02.svg +22 -0
  38. data/spec/sample_svg/tspan03.svg +21 -0
  39. data/spec/sample_svg/use.svg +17 -0
  40. data/spec/spec_helper.rb +9 -0
  41. metadata +103 -15
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .DS_Store
2
+ spec/sample_output/*.pdf
3
+ prawn-svg-*.gem
4
+ Gemfile.lock
5
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Declare your gem's dependencies in prawn-svg.gemspec.
4
+ # Bundler will treat runtime dependencies like base dependencies, and
5
+ # development dependencies will be added by default to the :development group.
6
+ gemspec
7
+
8
+ # Declare any dependencies that are still in development here instead of in
9
+ # your gemspec. These might include edge Rails or gems from your path or
10
+ # Git. Remember to move these dependencies to your gemspec before releasing
11
+ # your gem to rubygems.org.
12
+
13
+ # To use debugger
14
+ # gem 'ruby-debug19', :require => 'ruby-debug'
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'bundler/gem_tasks'
4
+
5
+ begin
6
+ require 'rdoc/task'
7
+ rescue LoadError
8
+ require 'rdoc/rdoc'
9
+ require 'rake/rdoctask'
10
+ RDoc::Task = Rake::RDocTask
11
+ end
12
+
13
+ RDoc::Task.new(:rdoc) do |rdoc|
14
+ rdoc.rdoc_dir = 'rdoc'
15
+ rdoc.title = 'prawn-svg documentation'
16
+ rdoc.options << '--line-numbers'
17
+ rdoc.rdoc_files.include('README')
18
+ rdoc.rdoc_files.include('lib/**/*.rb')
19
+ end
20
+
21
+ require 'rspec/core/rake_task'
22
+
23
+ RSpec::Core::RakeTask.new(:spec) do |spec|
24
+ end
25
+
26
+ task :default => :spec
@@ -56,8 +56,13 @@ class Prawn::Svg::Element
56
56
  x, y = x.split(/\s+/) if y.nil?
57
57
  add_call_and_enter name, @document.distance(x), -@document.distance(y)
58
58
 
59
- when 'rotate'
60
- add_call_and_enter name, -arguments.first.to_f, :origin => [0, @document.y('0')]
59
+ when 'rotate'
60
+ rotation_arguments = arguments.first.split(/\s+/)
61
+ if (rotation_arguments.length == 3)
62
+ add_call_and_enter name, -rotation_arguments.first.to_f, :origin => [@document.x(rotation_arguments[1].to_f), @document.y(rotation_arguments[2].to_f)]
63
+ else
64
+ add_call_and_enter name, -arguments.first.to_f, :origin => [0, @document.y('0')]
65
+ end
61
66
 
62
67
  when 'scale'
63
68
  args = arguments.first.split(/\s+/)
@@ -1,5 +1,3 @@
1
- require 'iconv'
2
-
3
1
  class Prawn::Svg::Font
4
2
  BUILT_IN_FONTS = ["Courier", "Helvetica", "Times-Roman", "Symbol", "ZapfDingbats"]
5
3
 
@@ -59,7 +57,7 @@ class Prawn::Svg::Font
59
57
  def self.font_information(filename)
60
58
  File.open(filename, "r") do |f|
61
59
  x = f.read(12)
62
- table_count = x[4] * 256 + x[5]
60
+ table_count = x[4].ord * 256 + x[5].ord
63
61
  tables = f.read(table_count * 16)
64
62
 
65
63
  offset, length = table_count.times do |index|
@@ -86,7 +84,12 @@ class Prawn::Svg::Font
86
84
  field = data[offset..offset+length-1]
87
85
  names[name_id] = if platform_id == 0
88
86
  begin
89
- Iconv.iconv('UTF-8', 'UTF-16', field)
87
+ if field.respond_to?(:encode)
88
+ field.encode(Encoding::UTF16)
89
+ else
90
+ require "iconv"
91
+ Iconv.iconv('UTF-8', 'UTF-16', field)
92
+ end
90
93
  rescue
91
94
  field
92
95
  end
@@ -59,6 +59,9 @@ class Prawn::Svg::Parser
59
59
  "rect" => %w(width height),
60
60
  "path" => %w(d)
61
61
  }
62
+
63
+ USE_NEW_CIRCLE_CALL = Prawn::Document.new.respond_to?(:circle)
64
+ USE_NEW_ELLIPSE_CALL = Prawn::Document.new.respond_to?(:ellipse)
62
65
 
63
66
  def parse_element(element)
64
67
  attrs = element.attributes
@@ -106,11 +109,16 @@ class Prawn::Svg::Parser
106
109
  element.add_call "polygon", *points
107
110
 
108
111
  when 'circle'
109
- element.add_call "circle_at",
110
- [x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], :radius => distance(attrs['r'])
111
-
112
+ if USE_NEW_CIRCLE_CALL
113
+ element.add_call "circle",
114
+ [x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], distance(attrs['r'])
115
+ else
116
+ element.add_call "circle_at",
117
+ [x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], :radius => distance(attrs['r'])
118
+ end
119
+
112
120
  when 'ellipse'
113
- element.add_call "ellipse_at",
121
+ element.add_call USE_NEW_ELLIPSE_CALL ? "ellipse" : "ellipse_at",
114
122
  [x(attrs['cx'] || "0"), y(attrs['cy'] || "0")], distance(attrs['rx']), distance(attrs['ry'])
115
123
 
116
124
  when 'rect'
@@ -0,0 +1,5 @@
1
+ module Prawn
2
+ module Svg
3
+ VERSION = '0.9.1.11'
4
+ end
5
+ end
data/prawn-svg.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/prawn/svg/version', __FILE__)
3
+
4
+ spec = Gem::Specification.new do |gem|
5
+ gem.name = 'prawn-svg'
6
+ gem.version = Prawn::Svg::VERSION
7
+ gem.summary = "SVG renderer for Prawn PDF library"
8
+ gem.description = "SVG renderer for Prawn PDF library"
9
+ gem.has_rdoc = false
10
+ gem.author = "Roger Nesbitt"
11
+ gem.email = "roger@seriousorange.com"
12
+ gem.homepage = "http://github.com/mogest/prawn-svg"
13
+
14
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
15
+ gem.files = `git ls-files`.split("\n")
16
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ gem.name = "prawn-svg"
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency "prawn", ">= 0.8.4"
21
+ gem.add_development_dependency "rspec"
22
+ gem.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,58 @@
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 = mock
36
+ e.stub!(:name).and_return(name)
37
+ e.stub!(:attributes).and_return(attributes)
38
+
39
+ Prawn::Svg::Element.new(@document, e, [], {})
40
+ end
41
+
42
+ it "ignores tags it doesn't know about" do
43
+ calls = []
44
+ @parser.send :parse_element, mock_element("unknown")
45
+ calls.should == []
46
+ @document.warnings.length.should == 1
47
+ @document.warnings.first.should include("Unknown tag")
48
+ end
49
+
50
+ it "ignores tags that don't have all required attributes set" do
51
+ calls = []
52
+ @parser.send :parse_element, mock_element("ellipse", "rx" => "1")
53
+ calls.should == []
54
+ @document.warnings.length.should == 1
55
+ @document.warnings.first.should include("Must have attributes ry on tag ellipse")
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe Prawn::Svg::Parser::Path do
4
+ before :each do
5
+ @path = Prawn::Svg::Parser::Path.new
6
+ end
7
+
8
+ describe "command parsing" do
9
+ it "correctly parses a valid path" do
10
+ calls = []
11
+ @path.stub!(:run_path_command) {|*args| calls << args}
12
+ @path.parse("A12.34 -56.78 89B4 5 C 6,7 T QX 0")
13
+
14
+ calls.should == [
15
+ ["A", [12.34, -56.78, 89]],
16
+ ["B", [4, 5]],
17
+ ["C", [6, 7]],
18
+ ["T", []],
19
+ ["Q", []],
20
+ ["X", [0]]
21
+ ]
22
+ end
23
+
24
+ it "correctly parses an empty path" do
25
+ @path.should_not_receive(:run_path_command)
26
+ @path.parse("").should == []
27
+ @path.parse(" ").should == []
28
+ end
29
+
30
+ it "raises on invalid characters in the path" do
31
+ lambda {@path.parse("M 10 % 20")}.should raise_error(Prawn::Svg::Parser::Path::InvalidError)
32
+ end
33
+
34
+ it "raises on numerical data before a command letter" do
35
+ lambda {@path.parse("10 P")}.should raise_error(Prawn::Svg::Parser::Path::InvalidError)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Prawn::Svg::Interface do
4
+ describe "sample file rendering" do
5
+ root = "#{File.dirname(__FILE__)}/../.."
6
+ files = Dir["#{root}/spec/sample_svg/*.svg"]
7
+
8
+ it "has at least 10 SVG sample files to test" do
9
+ files.length.should >= 10
10
+ end
11
+
12
+ files.each do |file|
13
+ it "renders the #{File.basename file} sample file without warnings or crashing" do
14
+ Prawn::Document.generate("#{root}/spec/sample_output/#{File.basename file}.pdf") do
15
+ r = svg IO.read(file), :at => [0, y], :width => 612 - 72
16
+ warnings = r[:warnings].reject {|w| w =~ /Verdana/ && w =~ /is not a known font/ }
17
+ warnings.should == []
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Prawn::Svg::Document do
4
+ before(:each) do
5
+ @document = Prawn::Svg::Document.new("<svg></svg>", [100, 100], {})
6
+ end
7
+
8
+ describe :points do
9
+ it "converts a variety of measurement units to points" do
10
+ @document.send(:points, 32).should == 32.0
11
+ @document.send(:points, 32.0).should == 32.0
12
+ @document.send(:points, "32").should == 32.0
13
+ @document.send(:points, "32unknown").should == 32.0
14
+ @document.send(:points, "32pt").should == 32.0
15
+ @document.send(:points, "32in").should == 32.0 * 72
16
+ @document.send(:points, "32ft").should == 32.0 * 72 * 12
17
+ @document.send(:points, "32mm").should be_within(0.0001).of(32 * 72 * 0.0393700787)
18
+ @document.send(:points, "32cm").should be_within(0.0001).of(32 * 72 * 0.393700787)
19
+ @document.send(:points, "32m").should be_within(0.0001).of(32 * 72 * 39.3700787)
20
+
21
+ @document.send :instance_variable_set, "@actual_width", 600
22
+ @document.send :instance_variable_set, "@actual_height", 400
23
+ @document.send(:points, "50%").should == 300
24
+ @document.send(:points, "50%", :y).should == 200
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Prawn::Svg::Element do
4
+ before :each do
5
+ e = mock
6
+ e.stub!(:attributes).and_return({})
7
+ @element = Prawn::Svg::Element.new(nil, e, [], {})
8
+ end
9
+
10
+ describe :color_to_hex do
11
+ it "converts #xxx to a hex value" do
12
+ @element.send(:color_to_hex, "#9ab").should == "99aabb"
13
+ end
14
+
15
+ it "converts #xxxxxx to a hex value" do
16
+ @element.send(:color_to_hex, "#9ab123").should == "9ab123"
17
+ end
18
+
19
+ it "converts an html colour name to a hex value" do
20
+ @element.send(:color_to_hex, "White").should == "ffffff"
21
+ end
22
+
23
+ it "converts an rgb string to a hex value" do
24
+ @element.send(:color_to_hex, "rgb(16, 32, 48)").should == "102030"
25
+ @element.send(:color_to_hex, "rgb(-5, 50%, 120%)").should == "007fff"
26
+ end
27
+
28
+ it "scans the string and finds the first colour it can parse" do
29
+ @element.send(:color_to_hex, "function(#someurl, 0) nonexistent rgb( 3 ,4,5 ) white").should == "030405"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Prawn::Svg::Font do
4
+ describe :map_font_family_to_pdf_font do
5
+ it "matches a built in font" do
6
+ Prawn::Svg::Font.map_font_family_to_pdf_font("blah, 'courier', nothing").should == 'Courier'
7
+ end
8
+
9
+ it "matches a default font" do
10
+ Prawn::Svg::Font.map_font_family_to_pdf_font("serif").should == 'Times-Roman'
11
+ Prawn::Svg::Font.map_font_family_to_pdf_font("blah, serif").should == 'Times-Roman'
12
+ Prawn::Svg::Font.map_font_family_to_pdf_font("blah, serif , test").should == 'Times-Roman'
13
+ end
14
+
15
+ if !Prawn::Svg::Font.installed_fonts.empty?
16
+ it "matches a font installed on the system" do
17
+ Prawn::Svg::Font.map_font_family_to_pdf_font("verdana, sans-serif").should == 'verdana'
18
+ Prawn::Svg::Font.map_font_family_to_pdf_font("VERDANA, sans-serif").should == 'verdana'
19
+ Prawn::Svg::Font.map_font_family_to_pdf_font("something, \"Times New Roman\", serif").should == "times new roman"
20
+ Prawn::Svg::Font.map_font_family_to_pdf_font("something, Times New Roman, serif").should == "times new roman"
21
+ end
22
+ else
23
+ it "not running font test because we couldn't find a font directory"
24
+ end
25
+
26
+ it "returns nil if it can't find any such font" do
27
+ Prawn::Svg::Font.map_font_family_to_pdf_font("blah, thing").should be_nil
28
+ Prawn::Svg::Font.map_font_family_to_pdf_font("").should be_nil
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,4 @@
1
+ require File.dirname(__FILE__) + '/../../../spec_helper'
2
+
3
+ describe Prawn::Svg::Parser::Text do
4
+ end
File without changes
@@ -0,0 +1,21 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
3
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+ <svg width="12cm" height="5.25cm" viewBox="0 0 1200 400"
5
+ xmlns="http://www.w3.org/2000/svg" version="1.1">
6
+ <title>Example arcs01 - arc commands in path data</title>
7
+ <desc>Picture of a pie chart with two pie wedges and
8
+ a picture of a line with arc blips</desc>
9
+ <rect x="1" y="1" width="1198" height="398"
10
+ fill="none" stroke="blue" stroke-width="1" />
11
+ <path d="M300,200 h-150 a150,150 0 1,0 150,-150 z"
12
+ fill="red" stroke="blue" stroke-width="5" />
13
+ <path d="M275,175 v-150 a150,150 0 0,0 -150,150 z"
14
+ fill="yellow" stroke="blue" stroke-width="5" />
15
+ <path d="M600,350 l 50,-25
16
+ a25,25 -30 0,1 50,-25 l 50,-25
17
+ a25,50 -30 0,1 50,-25 l 50,-25
18
+ a25,75 -30 0,1 50,-25 l 50,-25
19
+ a25,100 -30 0,1 50,-25 l 50,-25"
20
+ fill="none" stroke="red" stroke-width="5" />
21
+ </svg>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
3
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+ <svg width="12cm" height="4cm" viewBox="0 0 1200 400"
5
+ xmlns="http://www.w3.org/2000/svg" version="1.1">
6
+ <desc>Example circle01 - circle filled with red and stroked with blue</desc>
7
+ <!-- Show outline of canvas using 'rect' element -->
8
+ <rect x="1" y="1" width="1198" height="398"
9
+ fill="none" stroke="blue" stroke-width="2"/>
10
+ <circle cx="600" cy="200" r="100"
11
+ fill="red" stroke="blue" stroke-width="10" />
12
+ </svg>
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
3
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+ <svg width="5cm" height="4cm" viewBox="0 0 500 400"
5
+ xmlns="http://www.w3.org/2000/svg" version="1.1">
6
+ <title>Example cubic01- cubic Bézier commands in path data</title>
7
+ <desc>Picture showing a simple example of path data
8
+ using both a "C" and an "S" command,
9
+ along with annotations showing the control points
10
+ and end points</desc>
11
+ <style type="text/css"><![CDATA[
12
+ .Border { fill:none; stroke:blue; stroke-width:1 }
13
+ .Connect { fill:none; stroke:#888888; stroke-width:2 }
14
+ .SamplePath { fill:none; stroke:red; stroke-width:5 }
15
+ .EndPoint { fill:none; stroke:#888888; stroke-width:2 }
16
+ .CtlPoint { fill:#888888; stroke:none }
17
+ .AutoCtlPoint { fill:none; stroke:blue; stroke-width:4 }
18
+ .Label { font-size:22; font-family:Verdana }
19
+ ]]></style>
20
+ <rect class="Border" x="1" y="1" width="498" height="398" />
21
+ <polyline class="Connect" points="100,200 100,100" />
22
+ <polyline class="Connect" points="250,100 250,200" />
23
+ <polyline class="Connect" points="250,200 250,300" />
24
+ <polyline class="Connect" points="400,300 400,200" />
25
+ <path style=" fill:none; stroke:red; stroke-width:5" d="M100,200 C100,100 250,100 250,200
26
+ S400,300 400,200" />
27
+ <circle class="EndPoint" cx="100" cy="200" r="10" />
28
+ <circle class="EndPoint" cx="250" cy="200" r="10" />
29
+ <circle class="EndPoint" cx="400" cy="200" r="10" />
30
+ <circle class="CtlPoint" cx="100" cy="100" r="10" />
31
+ <circle class="CtlPoint" cx="250" cy="100" r="10" />
32
+ <circle class="CtlPoint" cx="400" cy="300" r="10" />
33
+ <circle class="AutoCtlPoint" cx="250" cy="300" r="9" />
34
+ <text class="Label" x="25" y="70">M100,200 C100,100 250,100 250,200</text>
35
+ <text class="Label" x="325" y="350"
36
+ style="text-anchor:middle">S400,300 400,200</text>
37
+ </svg>