prawn-svg 0.9.1.3 → 0.9.1.4

Sign up to get free protection for your applications and to get access to all the features.
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