prawn-svg 0.22.1 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -13
  3. data/lib/prawn-svg.rb +7 -0
  4. data/lib/prawn/svg/attributes.rb +1 -1
  5. data/lib/prawn/svg/attributes/color.rb +5 -0
  6. data/lib/prawn/svg/attributes/display.rb +1 -1
  7. data/lib/prawn/svg/attributes/font.rb +11 -11
  8. data/lib/prawn/svg/attributes/opacity.rb +3 -3
  9. data/lib/prawn/svg/css.rb +40 -0
  10. data/lib/prawn/svg/document.rb +23 -8
  11. data/lib/prawn/svg/elements/base.rb +20 -10
  12. data/lib/prawn/svg/elements/container.rb +1 -1
  13. data/lib/prawn/svg/elements/gradient.rb +7 -4
  14. data/lib/prawn/svg/elements/image.rb +2 -6
  15. data/lib/prawn/svg/elements/root.rb +4 -0
  16. data/lib/prawn/svg/elements/text.rb +14 -14
  17. data/lib/prawn/svg/font.rb +9 -91
  18. data/lib/prawn/svg/font_registry.rb +73 -0
  19. data/lib/prawn/svg/interface.rb +9 -22
  20. data/lib/prawn/svg/loaders/data.rb +18 -0
  21. data/lib/prawn/svg/loaders/file.rb +66 -0
  22. data/lib/prawn/svg/loaders/web.rb +28 -0
  23. data/lib/prawn/svg/state.rb +39 -0
  24. data/lib/prawn/svg/ttf.rb +61 -0
  25. data/lib/prawn/svg/url_loader.rb +35 -23
  26. data/lib/prawn/svg/version.rb +1 -1
  27. data/spec/integration_spec.rb +7 -6
  28. data/spec/prawn/svg/attributes/font_spec.rb +8 -5
  29. data/spec/prawn/svg/css_spec.rb +24 -0
  30. data/spec/prawn/svg/document_spec.rb +35 -10
  31. data/spec/prawn/svg/elements/base_spec.rb +32 -10
  32. data/spec/prawn/svg/elements/gradient_spec.rb +1 -1
  33. data/spec/prawn/svg/elements/text_spec.rb +4 -4
  34. data/spec/prawn/svg/font_registry_spec.rb +54 -0
  35. data/spec/prawn/svg/font_spec.rb +0 -27
  36. data/spec/prawn/svg/loaders/data_spec.rb +55 -0
  37. data/spec/prawn/svg/loaders/file_spec.rb +84 -0
  38. data/spec/prawn/svg/loaders/web_spec.rb +37 -0
  39. data/spec/prawn/svg/ttf_spec.rb +32 -0
  40. data/spec/prawn/svg/url_loader_spec.rb +90 -24
  41. data/spec/sample_svg/image03.svg +30 -0
  42. data/spec/sample_svg/tspan03-cc.svg +21 -0
  43. data/spec/sample_ttf/OpenSans-SemiboldItalic.ttf +0 -0
  44. data/spec/spec_helper.rb +17 -2
  45. metadata +28 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d93ba261ddc059e43122549f72a3e3daa676171f
4
- data.tar.gz: d0497cb84295f9202f84c62b5db79f9d29608019
3
+ metadata.gz: 0455251b1d5e7c430660f51c4843d26adb8d9913
4
+ data.tar.gz: caf87db1d427b7b528b5ae09b826b4dddec7a8a0
5
5
  SHA512:
6
- metadata.gz: acf2f6d51a5fda648bed5efd25ba85de91887867badccde3ce4d713cebddcc851280d524ba3006d7014651cd73ccf8c5a54185038a41dfa37612b522cb99ea17
7
- data.tar.gz: 2dc23eac65fc5be3299015309d8e2f94d5598ba016f7776090f886fe9bd4551ae90bd129252428b65b617bf8472ca4adcf9cdc5760f3e5f3f635793415d09622
6
+ metadata.gz: e1068767350107317ce264382ead9432a8ca98118d82301a402ef5fb50f6da65bdc2f547101955911cd24cf291745dd3f7b55f51d2374c96b9442673833df638
7
+ data.tar.gz: 47b83cd0def9edaec474ec458fa01831801abefadeebe797840b64bd7b3b2f38ff869e135893bb16a9f23b6a9fea28ab46c9f843775fa1c3f72bcd11964d8b33
data/README.md CHANGED
@@ -7,7 +7,7 @@ An SVG renderer for the Prawn PDF library.
7
7
 
8
8
  This will take an SVG file as input and render it into your PDF. Find out more about the Prawn PDF library at:
9
9
 
10
- http://wiki.github.com/sandal/prawn/
10
+ http://github.com/prawnpdf/prawn
11
11
 
12
12
  prawn-svg is compatible with all versions of Prawn from 0.11.1 onwards, including the 1.x and 2.x series.
13
13
  The minimum Ruby version required is 2.0.0.
@@ -15,23 +15,39 @@ The minimum Ruby version required is 2.0.0.
15
15
  ## Using prawn-svg
16
16
 
17
17
  ```ruby
18
- Prawn::Document.generate("svg.pdf") do
19
- svg svg_data, :at => [x, y], :width => w
18
+ Prawn::Document.generate("test.pdf") do
19
+ svg '<svg><rect width="100" height="100" fill="red"></rect></svg>'
20
20
  end
21
21
  ```
22
22
 
23
- Supply <tt>:at</tt> if you want to render it at a specific location on the page.
24
- Use <tt>:position</tt> with a value of <tt>:left</tt>, <tt>:center</tt>, <tt>:right</tt> or a number to render it at the current cursor position, or use <tt>:vposition</tt> with a value
25
- of <tt>:top</tt>, <tt>:center</tt>, <tt>:bottom</tt> or a number to specify its Y position too.
23
+ prawn-svg will do something sensible if you call it with only an SVG document, but you can also
24
+ pass the following options to tailor its operation:
26
25
 
27
- Either <tt>:width</tt>, <tt>:height</tt>, or neither may be specified; if neither is present,
28
- the dimensions specified in the SVG will be used, or if the dimensions aren't specified, it'll
29
- fit to the space available on the page.
26
+ Option | Data type | Description
27
+ ----------- | --------- | -----------
28
+ :at | [integer, integer] | Specify the location on the page you want the SVG to appear.
29
+ :position | :left, :center, :right, integer | If :at not specified, specifies the horizontal position to show the SVG. Defaults to :left.
30
+ :vposition | :top, :center, :bottom, integer | If :at not specified, specifies the vertical position to show the SVG. Defaults to current cursor position.
31
+ :width | integer | Desired width of the SVG. Defaults to horizontal space available.
32
+ :height | integer | Desired height of the SVG. Defaults to vertical space available.
33
+ :enable_web_requests | boolean | If true, prawn-svg will make http and https requests to fetch images. Defaults to true.
34
+ :enable_file_requests_with_root | string | If not nil, prawn-svg will serve `file:` URLs from your local disk if the file is located under the specified directory. It is very dangerous to specify the root path ("/") if you're not fully in control of your input SVG. Defaults to `nil` (off).
35
+ :cache_images | boolean | If true, prawn-svg will cache the result of all URL requests. Defaults to false.
36
+ :fallback_font_name | string | A font name which will override the default fallback font of Times-Roman. If this value is set to <tt>nil</tt>, prawn-svg will ignore a request for an unknown font and log a warning.
30
37
 
31
- <tt>:cache_images</tt>, if set to true, will cache images per document based on their URL.
38
+ ## Examples
32
39
 
33
- <tt>:fallback_font_name</tt> takes a font name which will override the default fallback font of Times-Roman.
34
- If this value is set to <tt>nil</tt>, prawn-svg will ignore a request for an unknown font and log a warning.
40
+ ```ruby
41
+ # Render the logo contained in the file logo.svg at 100, 100 with a width of 300
42
+ svg IO.read("logo.svg"), at: [100, 100], width: 300
43
+
44
+ # Render the logo at the current Y cursor position, centered in the current bounding box
45
+ svg IO.read("logo.svg"), position: :center
46
+
47
+ # Render the logo at the current Y cursor position, and serve file: links relative to its directory
48
+ root_path = "/apps/myapp/current/images"
49
+ svg IO.read("#{root_path}/logo.svg"), enable_file_requests_with_root: root_path
50
+ ```
35
51
 
36
52
  ## Supported features
37
53
 
@@ -90,7 +106,7 @@ By default, prawn-svg has a fonts path of <tt>["/Library/Fonts", "/System/Librar
90
106
  Mac OS X and Debian Linux users. You can add to the font path:
91
107
 
92
108
  ```ruby
93
- Prawn::SVG::Interface.font_path << "/my/font/directory"
109
+ Prawn::SVG::FontRegistry.font_path << "/my/font/directory"
94
110
  ```
95
111
 
96
112
 
data/lib/prawn-svg.rb CHANGED
@@ -3,17 +3,24 @@ require 'rexml/document'
3
3
  require 'prawn'
4
4
  require 'prawn/svg/version'
5
5
 
6
+ require 'prawn/svg/font_registry'
6
7
  require 'prawn/svg/calculators/aspect_ratio'
7
8
  require 'prawn/svg/calculators/document_sizing'
8
9
  require 'prawn/svg/calculators/pixels'
9
10
  require 'prawn/svg/url_loader'
11
+ require 'prawn/svg/loaders/data'
12
+ require 'prawn/svg/loaders/file'
13
+ require 'prawn/svg/loaders/web'
10
14
  require 'prawn/svg/color'
11
15
  require 'prawn/svg/attributes'
12
16
  require 'prawn/svg/elements'
13
17
  require 'prawn/svg/extension'
14
18
  require 'prawn/svg/interface'
19
+ require 'prawn/svg/css'
20
+ require 'prawn/svg/ttf'
15
21
  require 'prawn/svg/font'
16
22
  require 'prawn/svg/document'
23
+ require 'prawn/svg/state'
17
24
 
18
25
  module Prawn
19
26
  Svg = SVG # backwards compatibility
@@ -1,6 +1,6 @@
1
1
  module Prawn::SVG::Attributes
2
2
  end
3
3
 
4
- %w(transform opacity clip_path stroke font display).each do |name|
4
+ %w(transform opacity clip_path stroke font display color).each do |name|
5
5
  require "prawn/svg/attributes/#{name}"
6
6
  end
@@ -0,0 +1,5 @@
1
+ module Prawn::SVG::Attributes::Color
2
+ def parse_color_attribute
3
+ state.color = attributes['color'] if attributes['color']
4
+ end
5
+ end
@@ -1,5 +1,5 @@
1
1
  module Prawn::SVG::Attributes::Display
2
2
  def parse_display_attribute
3
- @state[:display] = attributes['display'].strip if attributes['display']
3
+ state.display = attributes['display'].strip if attributes['display']
4
4
  end
5
5
  end
@@ -1,37 +1,37 @@
1
1
  module Prawn::SVG::Attributes::Font
2
2
  def parse_font_attributes_and_call
3
3
  if size = attributes['font-size']
4
- @state[:font_size] = size.to_f
4
+ state.font_size = size.to_f
5
5
  end
6
6
  if weight = attributes['font-weight']
7
7
  font_updated = true
8
- @state[:font_weight] = Prawn::SVG::Font.weight_for_css_font_weight(weight)
8
+ state.font_weight = Prawn::SVG::Font.weight_for_css_font_weight(weight)
9
9
  end
10
10
  if style = attributes['font-style']
11
11
  font_updated = true
12
- @state[:font_style] = style == 'italic' ? :italic : nil
12
+ state.font_style = style == 'italic' ? :italic : nil
13
13
  end
14
14
  if (family = attributes['font-family']) && family.strip != ""
15
15
  font_updated = true
16
- @state[:font_family] = family
16
+ state.font_family = family
17
17
  end
18
18
  if (anchor = attributes['text-anchor'])
19
- @state[:text_anchor] = anchor
19
+ state.text_anchor = anchor
20
20
  end
21
21
 
22
- if @state[:font_family] && font_updated
23
- usable_font_families = [@state[:font_family], document.fallback_font_name]
22
+ if state.font_family && font_updated
23
+ usable_font_families = [state.font_family, document.fallback_font_name]
24
24
 
25
25
  font_used = usable_font_families.compact.detect do |name|
26
- if font = Prawn::SVG::Font.load(name, @state[:font_weight], @state[:font_style])
27
- @state[:font_subfamily] = font.subfamily
28
- add_call_and_enter 'font', font.name, :style => @state[:font_subfamily]
26
+ if font = document.font_registry.load(name, state.font_weight, state.font_style)
27
+ state.font_subfamily = font.subfamily
28
+ add_call_and_enter 'font', font.name, :style => state.font_subfamily
29
29
  true
30
30
  end
31
31
  end
32
32
 
33
33
  if font_used.nil?
34
- warnings << "Font family '#{@state[:font_family]}' style '#{@state[:font_style] || 'normal'}' is not a known font, and the fallback font could not be found."
34
+ warnings << "Font family '#{state.font_family}' style '#{state.font_style || 'normal'}' is not a known font, and the fallback font could not be found."
35
35
  end
36
36
  end
37
37
  end
@@ -6,10 +6,10 @@ module Prawn::SVG::Attributes::Opacity
6
6
  stroke_opacity = clamp(attributes['stroke-opacity'].to_f, 0, 1) if attributes['stroke-opacity']
7
7
 
8
8
  if fill_opacity || stroke_opacity
9
- state[:fill_opacity] = (state[:fill_opacity] || 1) * (fill_opacity || 1)
10
- state[:stroke_opacity] = (state[:stroke_opacity] || 1) * (stroke_opacity || 1)
9
+ state.fill_opacity *= fill_opacity || 1
10
+ state.stroke_opacity *= stroke_opacity || 1
11
11
 
12
- add_call_and_enter 'transparent', state[:fill_opacity], state[:stroke_opacity]
12
+ add_call_and_enter 'transparent', state.fill_opacity, state.stroke_opacity
13
13
  end
14
14
  end
15
15
  end
@@ -0,0 +1,40 @@
1
+ class Prawn::SVG::CSS
2
+ class << self
3
+ def parse_font_family_string(string)
4
+ in_quote = nil
5
+ in_escape = false
6
+ current = nil
7
+ fonts = []
8
+
9
+ string.chars.each do |char|
10
+ if in_escape
11
+ in_escape = false
12
+ if current.nil?
13
+ current = char
14
+ fonts << current
15
+ else
16
+ current << char
17
+ end
18
+ elsif char == ',' && in_quote.nil?
19
+ current = nil
20
+ elsif char == in_quote
21
+ in_quote = nil
22
+ elsif in_quote.nil? && (char == '"' || char == "'")
23
+ in_quote = char
24
+ elsif char == '\\'
25
+ in_escape = true
26
+ elsif current.nil?
27
+ if char.match(/\s/).nil?
28
+ current = char
29
+ fonts << current
30
+ end
31
+ else
32
+ current << char
33
+ end
34
+ end
35
+
36
+ fonts.map(&:rstrip)
37
+ end
38
+ end
39
+ end
40
+
@@ -1,4 +1,7 @@
1
1
  class Prawn::SVG::Document
2
+ Error = Class.new(StandardError)
3
+ InvalidSVGData = Class.new(Error)
4
+
2
5
  begin
3
6
  require 'css_parser'
4
7
  CSS_PARSER_LOADED = true
@@ -10,23 +13,39 @@ class Prawn::SVG::Document
10
13
 
11
14
  # An +Array+ of warnings that occurred while parsing the SVG data.
12
15
  attr_reader :warnings
13
- attr_writer :url_cache
14
16
 
15
17
  attr_reader :root,
16
18
  :sizing,
17
- :cache_images, :fallback_font_name,
19
+ :fallback_font_name,
20
+ :font_registry,
21
+ :url_loader,
18
22
  :css_parser, :elements_by_id, :gradients
19
23
 
20
- def initialize(data, bounds, options)
24
+ def initialize(data, bounds, options, font_registry: nil)
21
25
  @css_parser = CssParser::Parser.new if CSS_PARSER_LOADED
22
26
 
23
27
  @root = REXML::Document.new(data).root
28
+
29
+ if @root.nil?
30
+ if data.respond_to?(:end_with?) && data.end_with?(".svg")
31
+ raise InvalidSVGData, "The data supplied is not a valid SVG document. It looks like you've supplied a filename instead; use IO.read(filename) to get the data before you pass it to prawn-svg."
32
+ else
33
+ raise InvalidSVGData, "The data supplied is not a valid SVG document."
34
+ end
35
+ end
36
+
24
37
  @warnings = []
25
38
  @options = options
26
39
  @elements_by_id = {}
27
40
  @gradients = {}
28
- @cache_images = options[:cache_images]
29
41
  @fallback_font_name = options.fetch(:fallback_font_name, DEFAULT_FALLBACK_FONT_NAME)
42
+ @font_registry = font_registry
43
+
44
+ @url_loader = Prawn::SVG::UrlLoader.new(
45
+ enable_cache: options[:cache_images],
46
+ enable_web: options.fetch(:enable_web_requests, true),
47
+ enable_file_with_root: options[:enable_file_requests_with_root]
48
+ )
30
49
 
31
50
  @sizing = Prawn::SVG::Calculators::DocumentSizing.new(bounds, @root.attributes)
32
51
  sizing.requested_width = options[:width]
@@ -53,8 +72,4 @@ class Prawn::SVG::Document
53
72
  def points(value, axis = nil)
54
73
  Prawn::SVG::Calculators::Pixels.to_pixels(value, @axis_to_size.fetch(axis, sizing.viewport_diagonal))
55
74
  end
56
-
57
- def url_loader
58
- @url_loader ||= Prawn::SVG::UrlLoader.new(:enable_cache => cache_images)
59
- end
60
75
  end
@@ -7,6 +7,7 @@ class Prawn::SVG::Elements::Base
7
7
  include Prawn::SVG::Attributes::Stroke
8
8
  include Prawn::SVG::Attributes::Font
9
9
  include Prawn::SVG::Attributes::Display
10
+ include Prawn::SVG::Attributes::Color
10
11
 
11
12
  COMMA_WSP_REGEXP = Prawn::SVG::Elements::COMMA_WSP_REGEXP
12
13
 
@@ -33,6 +34,7 @@ class Prawn::SVG::Elements::Base
33
34
 
34
35
  def process
35
36
  combine_attributes_and_style_declarations
37
+ parse_standard_attributes
36
38
  parse
37
39
 
38
40
  apply_calls_from_standard_attributes
@@ -85,7 +87,7 @@ class Prawn::SVG::Elements::Base
85
87
  if element_class = Prawn::SVG::Elements::TAG_CLASS_MAPPING[elem.name.to_sym]
86
88
  add_call "save"
87
89
 
88
- child = element_class.new(@document, elem, @calls, @state.dup)
90
+ child = element_class.new(@document, elem, @calls, state.dup)
89
91
  child.process
90
92
 
91
93
  add_call "restore"
@@ -107,8 +109,8 @@ class Prawn::SVG::Elements::Base
107
109
  end
108
110
 
109
111
  def apply_drawing_call(draw_types)
110
- if !@state[:disable_drawing] && !container?
111
- if draw_types.empty? || @state[:display] == "none"
112
+ if !state.disable_drawing && !container?
113
+ if draw_types.empty? || state.display == "none"
112
114
  add_call_and_enter("end_path")
113
115
  else
114
116
  add_call_and_enter(draw_types.join("_and_"))
@@ -116,30 +118,38 @@ class Prawn::SVG::Elements::Base
116
118
  end
117
119
  end
118
120
 
121
+ def parse_standard_attributes
122
+ parse_color_attribute
123
+ end
124
+
119
125
  def parse_fill_and_stroke_attributes_and_call
120
126
  ["fill", "stroke"].select do |type|
121
127
  case keyword = attribute_value_as_keyword(type)
122
128
  when nil
123
129
  when 'inherit'
124
130
  when 'none'
125
- state[type.to_sym] = false
131
+ state.disable_draw_type(type)
126
132
  else
127
- state[type.to_sym] = false
128
- color_attribute = keyword == 'currentcolor' ? 'color' : type
129
- color = @attributes[color_attribute]
133
+ state.disable_draw_type(type)
134
+
135
+ if keyword == 'currentcolor'
136
+ color = state.color
137
+ else
138
+ color = @attributes[type]
139
+ end
130
140
 
131
141
  results = Prawn::SVG::Color.parse(color, document.gradients)
132
142
 
133
143
  results.each do |result|
134
144
  case result
135
145
  when Prawn::SVG::Color::Hex
136
- state[type.to_sym] = true
146
+ state.enable_draw_type(type)
137
147
  add_call "#{type}_color", result.value
138
148
  break
139
149
  when Prawn::SVG::Elements::Gradient
140
150
  arguments = result.gradient_arguments(self)
141
151
  if arguments
142
- state[type.to_sym] = true
152
+ state.enable_draw_type(type)
143
153
  add_call "#{type}_gradient", **arguments
144
154
  break
145
155
  end
@@ -147,7 +157,7 @@ class Prawn::SVG::Elements::Base
147
157
  end
148
158
  end
149
159
 
150
- state[type.to_sym]
160
+ state.draw_type(type)
151
161
  end
152
162
  end
153
163
 
@@ -1,6 +1,6 @@
1
1
  class Prawn::SVG::Elements::Container < Prawn::SVG::Elements::Base
2
2
  def parse
3
- state[:disable_drawing] = true if name == "clipPath"
3
+ state.disable_drawing = true if name == "clipPath"
4
4
  end
5
5
 
6
6
  def apply
@@ -42,9 +42,12 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
42
42
  end
43
43
 
44
44
  def assert_compatible_prawn_version
45
- if (Prawn::VERSION.split(".").map(&:to_i) <=> [2, 0, 4]) == -1
46
- raise SkipElementError, "Prawn 2.0.4+ must be used if you'd like prawn-svg to render gradients"
47
- end
45
+ # At the moment, the patch required for this functionality to work in prawn has not been merged.
46
+ raise SkipElementError, "We are unfortunately still waiting on the Prawn project to merge a pull request that is required for this feature to correctly function"
47
+
48
+ # if (Prawn::VERSION.split(".").map(&:to_i) <=> [2, 0, 4]) == -1
49
+ # raise SkipElementError, "Prawn 2.0.4+ must be used if you'd like prawn-svg to render gradients"
50
+ # end
48
51
  end
49
52
 
50
53
  def load_gradient_configuration
@@ -80,7 +83,7 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
80
83
 
81
84
  def load_stops
82
85
  stop_elements = source.elements.map do |child|
83
- element = Prawn::SVG::Elements::Base.new(document, child, [], {})
86
+ element = Prawn::SVG::Elements::Base.new(document, child, [], Prawn::SVG::State.new)
84
87
  element.process
85
88
  element
86
89
  end.select do |element|
@@ -13,17 +13,13 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
13
13
  def parse
14
14
  require_attributes 'width', 'height'
15
15
 
16
- raise SkipElementQuietly if state[:display] == "none"
16
+ raise SkipElementQuietly if state.display == "none"
17
17
 
18
18
  @url = attributes['xlink:href'] || attributes['href']
19
19
  if @url.nil?
20
20
  raise SkipElementError, "image tag must have an xlink:href"
21
21
  end
22
22
 
23
- if !@document.url_loader.valid?(@url)
24
- raise SkipElementError, "image tag xlink:href attribute must use http, https or data scheme"
25
- end
26
-
27
23
  x = x(attributes['x'] || 0)
28
24
  y = y(attributes['y'] || 0)
29
25
  width = distance(attributes['width'])
@@ -34,7 +30,7 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
34
30
 
35
31
  @image = begin
36
32
  @document.url_loader.load(@url)
37
- rescue => e
33
+ rescue Prawn::SVG::UrlLoader::Error => e
38
34
  raise SkipElementError, "Error retrieving URL #{@url}: #{e.message}"
39
35
  end
40
36