prawn-svg 0.9.1.3 → 0.9.1.4

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.
data/README CHANGED
@@ -32,6 +32,8 @@ prawn-svg is in its infancy and does not support the full SVG specifications. I
32
32
  attributes: size, text-anchor
33
33
  partially supported attributes: font-family
34
34
 
35
+ - style tag, if css_parser gem is installed on the system [1]
36
+
35
37
  - attributes/styles: fill, stroke, stroke-width, opacity, fill-opacity, stroke-opacity, transform
36
38
 
37
39
  - transform methods: translate, rotate, scale
@@ -40,4 +42,6 @@ prawn-svg is in its infancy and does not support the full SVG specifications. I
40
42
 
41
43
  - measurements specified in pt, cm, dm, ft, in, m, mm, yd
42
44
 
43
- prawn-svg does NOT support CSS classes, named elements, anything in the defs tag, the tspan tag, gradients/patterns or markers.
45
+ prawn-svg does NOT support named elements, external references, the tspan tag, gradients/patterns or markers.
46
+
47
+ [1] If the css_parser gem is installed, it will handle CSS style definitions, but only simple tag, class or id definitions.
@@ -12,6 +12,13 @@ require 'rexml/document'
12
12
  # SVG to another format.
13
13
  #
14
14
  class Prawn::Svg::Parser
15
+ begin
16
+ require 'css_parser'
17
+ CSS_PARSER_LOADED = true
18
+ rescue LoadError
19
+ CSS_PARSER_LOADED = false
20
+ end
21
+
15
22
  include Prawn::Measurements
16
23
 
17
24
  attr_reader :width, :height
@@ -32,6 +39,7 @@ class Prawn::Svg::Parser
32
39
  @data = data
33
40
  @options = options
34
41
  @warnings = []
42
+ @css_parser = CssParser::Parser.new if CSS_PARSER_LOADED
35
43
 
36
44
  if data
37
45
  parse_document
@@ -82,50 +90,34 @@ class Prawn::Svg::Parser
82
90
 
83
91
  def parse_element(element, calls, state)
84
92
  attrs = element.attributes
93
+ calls, style_attrs = apply_styles(element, calls, state)
85
94
 
86
- if transform = attrs['transform']
87
- parse_css_method_calls(transform).each do |name, arguments|
88
- case name
89
- when 'translate'
90
- x, y = arguments
91
- x, y = x.split(/\s+/) if y.nil?
92
- calls << [name, [distance(x), -distance(y)], []]
93
- calls = calls.last.last
94
- when 'rotate'
95
- calls << [name, [-arguments.first.to_f, {:origin => [0, y('0')]}], []]
96
- calls = calls.last.last
97
- when 'scale'
98
- calls << [name, [arguments.first.to_f], []]
99
- calls = calls.last.last
100
- else
101
- @warnings << "Unknown transformation '#{name}'; ignoring"
102
- end
103
- end
104
- end
105
-
106
- calls, style_attrs, draw_type = apply_styles(attrs, calls, state)
107
-
108
- state[:draw_type] = draw_type if draw_type != ""
109
- if state[:draw_type] && !%w(g svg).include?(element.name)
110
- calls << [state[:draw_type], [], []]
111
- calls = calls.last.last
112
- end
113
-
114
95
  if required_attributes = REQUIRED_ATTRIBUTES[element.name]
115
96
  return unless check_attrs_present(element, required_attributes)
116
97
  end
117
-
98
+
118
99
  case element.name
119
- when 'defs', 'desc'
120
- # ignore these tags
100
+ when 'title', 'desc'
101
+ # ignore
121
102
 
122
103
  when 'g', 'svg'
123
104
  element.elements.each do |child|
124
105
  parse_element(child, calls, state.dup)
125
106
  end
107
+
108
+ when 'defs'
109
+ # Pass calls as a blank array so that nothing under this tag can be added to our call tree.
110
+ element.elements.each do |child|
111
+ parse_element(child, [], state.dup.merge(:display => false))
112
+ end
113
+
114
+ when 'style'
115
+ load_css_styles(element)
126
116
 
127
117
  when 'text'
128
- # very primitive support for fonts
118
+ # Very primitive support for font-family; it won't work in most cases because
119
+ # PDF only has a few built-in fonts, and they're not the same as the names
120
+ # used typically with the web fonts.
129
121
  if (font = style_attrs['font-family']) && !font.match(/[\/\\]/)
130
122
  font = font.strip
131
123
  if font != ""
@@ -139,7 +131,8 @@ class Prawn::Svg::Parser
139
131
  opts[:size] = size.to_f * @scale
140
132
  end
141
133
 
142
- # This is not a prawn option but we can't work out how to render it here - it's handled by #rewrite_call
134
+ # This is not a prawn option but we can't work out how to render it here -
135
+ # it's handled by Svg#rewrite_call_arguments
143
136
  if anchor = style_attrs['text-anchor']
144
137
  opts[:text_anchor] = anchor
145
138
  end
@@ -213,6 +206,18 @@ class Prawn::Svg::Parser
213
206
  end
214
207
  end
215
208
 
209
+ def load_css_styles(element)
210
+ if @css_parser
211
+ data = if element.cdatas.any?
212
+ element.cdatas.collect(&:to_s).join
213
+ else
214
+ element.text
215
+ end
216
+
217
+ @css_parser.add_block!(data)
218
+ end
219
+ end
220
+
216
221
  def parse_css_declarations(declarations)
217
222
  # copied from css_parser
218
223
  declarations.gsub!(/(^[\s]*)|([\s]*$)/, '')
@@ -227,11 +232,55 @@ class Prawn::Svg::Parser
227
232
  end
228
233
  end
229
234
 
230
- def apply_styles(attrs, calls, state)
231
- draw_types = []
235
+ def determine_style_for(element)
236
+ if @css_parser
237
+ tag_style = @css_parser.find_by_selector(element.name)
238
+ id_style = @css_parser.find_by_selector("##{element.attributes["id"]}") if element.attributes["id"]
239
+
240
+ if classes = element.attributes["class"]
241
+ class_styles = classes.strip.split(/\s+/).collect do |class_name|
242
+ @css_parser.find_by_selector(".#{class_name}")
243
+ end
244
+ end
245
+
246
+ element_style = element.attributes['style']
247
+
248
+ style = [tag_style, class_styles, id_style, element_style].flatten.collect do |s|
249
+ s.nil? || s.strip == "" ? "" : "#{s}#{";" unless s.match(/;\s*\z/)}"
250
+ end.join
251
+ else
252
+ style = element.attributes['style'] || ""
253
+ end
232
254
 
233
- decs = attrs["style"] ? parse_css_declarations(attrs["style"]) : {}
234
- attrs.each {|n,v| decs[n] = v unless decs[n]}
255
+ decs = parse_css_declarations(style)
256
+ element.attributes.each {|n,v| decs[n] = v unless decs[n]}
257
+ decs
258
+ end
259
+
260
+ def apply_styles(element, calls, state)
261
+ decs = determine_style_for(element)
262
+ draw_types = []
263
+
264
+ # Transform
265
+ if transform = decs['transform']
266
+ parse_css_method_calls(transform).each do |name, arguments|
267
+ case name
268
+ when 'translate'
269
+ x, y = arguments
270
+ x, y = x.split(/\s+/) if y.nil?
271
+ calls << [name, [distance(x), -distance(y)], []]
272
+ calls = calls.last.last
273
+ when 'rotate'
274
+ calls << [name, [-arguments.first.to_f, {:origin => [0, y('0')]}], []]
275
+ calls = calls.last.last
276
+ when 'scale'
277
+ calls << [name, [arguments.first.to_f], []]
278
+ calls = calls.last.last
279
+ else
280
+ @warnings << "Unknown transformation '#{name}'; ignoring"
281
+ end
282
+ end
283
+ end
235
284
 
236
285
  # Opacity:
237
286
  # We can't do nested opacities quite like the SVG requires, but this is close enough.
@@ -247,6 +296,7 @@ class Prawn::Svg::Parser
247
296
  calls = calls.last.last
248
297
  end
249
298
 
299
+ # Fill and stroke
250
300
  if decs['fill'] && decs['fill'] != "none"
251
301
  if color = color_to_hex(decs['fill'])
252
302
  calls << ['fill_color', [color], []]
@@ -261,9 +311,16 @@ class Prawn::Svg::Parser
261
311
  draw_types << 'stroke'
262
312
  end
263
313
 
264
- calls << ['line_width', [distance(decs['stroke-width'])], []] if decs['stroke-width']
314
+ calls << ['line_width', [distance(decs['stroke-width'])], []] if decs['stroke-width']
315
+
316
+ draw_type = draw_types.join("_and_")
317
+ state[:draw_type] = draw_type if draw_type != ""
318
+ if state[:draw_type] && !%w(g svg).include?(element.name)
319
+ calls << [state[:draw_type], [], []]
320
+ calls = calls.last.last
321
+ end
265
322
 
266
- [calls, decs, draw_types.join("_and_")]
323
+ [calls, decs]
267
324
  end
268
325
 
269
326
  def parse_css_method_calls(string)
data/lib/prawn/svg/svg.rb CHANGED
@@ -49,17 +49,21 @@ class Prawn::Svg
49
49
 
50
50
  def issue_prawn_command(prawn, calls)
51
51
  calls.each do |call, arguments, children|
52
- if children.empty?
53
- rewrite_call_arguments(prawn, call, arguments)
54
- prawn.send(call, *arguments)
52
+ if rewrite_call_arguments(prawn, call, arguments) == false
53
+ issue_prawn_command(prawn, children) if children.any?
55
54
  else
56
- prawn.send(call, *arguments, &proc_creator(prawn, children))
55
+ if children.empty?
56
+ prawn.send(call, *arguments)
57
+ else
58
+ prawn.send(call, *arguments, &proc_creator(prawn, children))
59
+ end
57
60
  end
58
61
  end
59
62
  end
60
63
 
61
64
  def rewrite_call_arguments(prawn, call, arguments)
62
- if call == 'text_box'
65
+ case call
66
+ when 'text_box'
63
67
  if (anchor = arguments.last.delete(:text_anchor)) && %w(middle end).include?(anchor)
64
68
  width = prawn.width_of(*arguments)
65
69
  width /= 2 if anchor == 'middle'
@@ -67,6 +71,12 @@ class Prawn::Svg
67
71
  end
68
72
 
69
73
  arguments.last[:at][1] += prawn.height_of(*arguments) / 3 * 2
74
+
75
+ when 'font'
76
+ unless prawn.font_families.member?(arguments.first)
77
+ @parser_warnings << "#{arguments.first} is not a known font."
78
+ false
79
+ end
70
80
  end
71
81
  end
72
82
  end
metadata CHANGED
@@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version
6
6
  - 0
7
7
  - 9
8
8
  - 1
9
- - 3
10
- version: 0.9.1.3
9
+ - 4
10
+ version: 0.9.1.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - Roger Nesbitt
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-03-27 00:00:00 +13:00
18
+ date: 2010-03-31 00:00:00 +13:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency