prawn-svg 0.22.1 → 0.23.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.
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