prawn-svg 0.12.0.4 → 0.12.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 77d41d8457a9fc1b77f14802fb58ec0ba9afda1e
4
+ data.tar.gz: f5c44534f0c24368ecf755021b5fb6220f15e678
5
+ SHA512:
6
+ metadata.gz: 9bba222558b111e36c62d8d51568c0e78f3deb26e9e58c8844f38c2455520b756cb1fa9c8309b96c44c6ea1f40dcb27e02fd2ca6f7c4b8b6d2e9a4d730db2357
7
+ data.tar.gz: ac9fb650d66c5bb0847ea612f81626690c8b4b5b80212d4bbd11a9056f91d32865171257e6ababb6105c56d81703a035e9fab2a354e74ac4176b61c924c38b4c
data/.gitignore CHANGED
@@ -4,3 +4,4 @@ prawn-svg-*.gem
4
4
  Gemfile.lock
5
5
  .rvmrc
6
6
  .*.swp
7
+ .ruby-version
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License
2
2
 
3
- Copyright 2010 Roger Nesbitt
3
+ Copyright 2010-2013 Roger Nesbitt
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,5 +1,4 @@
1
- prawn-svg
2
- =========
1
+ # prawn-svg
3
2
 
4
3
  An SVG renderer for the Prawn PDF library.
5
4
 
@@ -7,15 +6,14 @@ This will take an SVG file as input and render it into your PDF. Find out more
7
6
 
8
7
  http://wiki.github.com/sandal/prawn/
9
8
 
10
- Using prawn-svg
11
- ---------------
9
+ ## Using prawn-svg
12
10
 
13
11
  ```ruby
14
12
  Prawn::Document.generate("svg.pdf") do
15
13
  svg svg_data, :at => [x, y], :width => w
16
14
  end
17
15
  ```
18
-
16
+
19
17
  <tt>:at</tt> must be specified.
20
18
 
21
19
  <tt>:width</tt>, <tt>:height</tt>, or neither may be specified; if neither is present,
@@ -23,31 +21,31 @@ the resolution specified in the SVG will be used.
23
21
 
24
22
  <tt>:cache_images</tt>, if set to true, will cache images per document based on their URL.
25
23
 
26
- Supported features
27
- ------------------
24
+ <tt>:fallback_font_name</tt> takes a font name which will override the default fallback font of Times-Roman.
25
+ If this value is set to <tt>nil</tt>, prawn-svg will ignore a request for an unknown font and log a warning.
26
+
27
+ ## Supported features
28
28
 
29
- prawn-svg is in its infancy and does not support the full SVG specifications. It currently supports:
29
+ prawn-svg does not support the full SVG specification. It currently supports:
30
+
31
+ - <tt>line</tt>, <tt>polyline</tt>, <tt>polygon</tt>, and <tt>circle</tt> tags
30
32
 
31
- - <tt>line</tt> tag
32
- - <tt>polyline</tt> tag
33
- - <tt>polygon</tt> tag
34
- - <tt>circle</tt> tag
35
33
  - <tt>ellipse</tt> tag (although this seems to be buggy)
36
-
34
+
37
35
  - <tt>rect</tt> tag
38
36
  supports rounded rects, but only one radius is applied to all corners
39
-
37
+
40
38
  - <tt>path</tt> tag
41
39
  supports moveto, closepath, lineto, horiz lineto, vert lineto, curveto, smooth curveto, quad curveto, smooth quad curveto
42
40
  does not support elliptical arc
43
-
41
+
44
42
  - <tt>text</tt> and <tt>tspan</tt> tags
45
43
  attributes: size, text-anchor, font-family, font-weight, dx, dy
46
-
44
+
47
45
  - <tt>svg</tt>, <tt>g</tt> and <tt>symbol</tt> tags
48
-
46
+
49
47
  - <tt>use</tt> tag
50
-
48
+
51
49
  - <tt>style</tt> tag, if css_parser gem is installed on the system (see CSS section below)
52
50
 
53
51
  - <tt>image</tt> tag, only with http/https schemes
@@ -55,32 +53,35 @@ prawn-svg is in its infancy and does not support the full SVG specifications. I
55
53
  - <tt>clipPath</tt> tag
56
54
 
57
55
  - attributes/styles: <tt>fill</tt>, <tt>stroke</tt>, <tt>stroke-width</tt>, <tt>opacity</tt>, <tt>fill-opacity</tt>, <tt>stroke-opacity</tt>, <tt>transform</tt>, <tt>clip-path</tt>
58
-
56
+
59
57
  - transform methods: <tt>translate</tt>, <tt>rotate</tt>, <tt>scale</tt>, <tt>matrix</tt>
60
-
58
+
61
59
  - colors: HTML standard names, <tt>#xxx</tt>, <tt>#xxxxxx</tt>, <tt>rgb(1, 2, 3)</tt>, <tt>rgb(1%, 2%, 3%)</tt>
62
-
63
- - measurements specified in <tt>pt</tt>, <tt>cm</tt>, <tt>dm</tt>, <tt>ft</tt>, <tt>in</tt>, <tt>m</tt>, <tt>mm</tt>, <tt>yd</tt>, <tt>%</tt>
64
-
65
- - fonts: generic CSS fonts, built in PDF fonts, and any TTF fonts in your fonts path
66
60
 
67
- By default, prawn-svg has a fonts path of <tt>["/Library/Fonts", "/usr/share/fonts/truetype/**"]</tt> to catch
68
- Mac OS X and Debian Linux users. You can add to the font path:
61
+ - measurements specified in <tt>pt</tt>, <tt>cm</tt>, <tt>dm</tt>, <tt>ft</tt>, <tt>in</tt>, <tt>m</tt>, <tt>mm</tt>, <tt>yd</tt>, <tt>%</tt>
69
62
 
70
- ```ruby
71
- Prawn::Svg::Interface.font_path << "/my/font/directory"
72
- ```
63
+ - fonts: generic CSS fonts, built in PDF fonts, and any TTF fonts in your fonts path
73
64
 
74
- CSS
75
- ---
65
+ ## CSS
76
66
 
77
67
  If the css_parser gem is installed, it will handle CSS style definitions, but only simple tag, class or id definitions. It's very basic
78
68
  so do not expect too much of it.
79
69
 
80
- Not supported
81
- -------------
70
+ ## Not supported
82
71
 
83
72
  prawn-svg does NOT support external references, measurements in en or em, sub-viewports, gradients/patterns or markers.
84
73
 
74
+ ## Configuration
75
+
76
+ ### Fonts
77
+
78
+ By default, prawn-svg has a fonts path of <tt>["/Library/Fonts", "/System/Library/Fonts", "#{ENV["HOME"]}/Library/Fonts", "/usr/share/fonts/truetype/**"]</tt> to catch
79
+ Mac OS X and Debian Linux users. You can add to the font path:
80
+
81
+ ```ruby
82
+ Prawn::Svg::Interface.font_path << "/my/font/directory"
83
+ ```
84
+
85
+
85
86
  --
86
87
  Copyright Roger Nesbitt <roger@seriousorange.com>. MIT licence.
data/lib/prawn-svg.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'prawn'
2
+ require 'prawn/svg/color'
2
3
  require 'prawn/svg/extension'
3
4
  require 'prawn/svg/interface'
4
5
  require 'prawn/svg/font'
@@ -0,0 +1,35 @@
1
+ class Prawn::Svg::Color
2
+ # TODO : use http://www.w3.org/TR/SVG11/types.html#ColorKeywords
3
+ HTML_COLORS = {
4
+ 'black' => "000000", 'green' => "008000", 'silver' => "c0c0c0", 'lime' => "00ff00",
5
+ 'gray' => "808080", 'olive' => "808000", 'white' => "ffffff", 'yellow' => "ffff00",
6
+ 'maroon' => "800000", 'navy' => "000080", 'red' => "ff0000", 'blue' => "0000ff",
7
+ 'purple' => "800080", 'teal' => "008080", 'fuchsia' => "ff00ff", 'aqua' => "00ffff"
8
+ }.freeze
9
+
10
+ RGB_VALUE_REGEXP = "\s*(-?[0-9.]+%?)\s*"
11
+ RGB_REGEXP = /\Argb\(#{RGB_VALUE_REGEXP},#{RGB_VALUE_REGEXP},#{RGB_VALUE_REGEXP}\)\z/i
12
+
13
+ def self.color_to_hex(color_string)
14
+ color_string.scan(/([^(\s]+(\([^)]*\))?)/).detect do |color, *_|
15
+ if m = color.match(/\A#([0-9a-f])([0-9a-f])([0-9a-f])\z/i)
16
+ break "#{m[1] * 2}#{m[2] * 2}#{m[3] * 2}"
17
+ elsif color.match(/\A#[0-9a-f]{6}\z/i)
18
+ break color[1..6]
19
+ elsif hex = HTML_COLORS[color.downcase]
20
+ break hex
21
+ elsif m = color.match(RGB_REGEXP)
22
+ break (1..3).collect do |n|
23
+ value = m[n].to_f
24
+ value *= 2.55 if m[n][-1..-1] == '%'
25
+ "%02x" % clamp(value.round, 0, 255)
26
+ end.join
27
+ end
28
+ end
29
+ end
30
+
31
+ protected
32
+ def self.clamp(value, min_value, max_value)
33
+ [[value, min_value].max, max_value].min
34
+ end
35
+ end
@@ -10,6 +10,7 @@ class Prawn::Svg::Document
10
10
 
11
11
  DEFAULT_WIDTH = 640
12
12
  DEFAULT_HEIGHT = 480
13
+ DEFAULT_FALLBACK_FONT_NAME = "Times-Roman"
13
14
 
14
15
  # An +Array+ of warnings that occurred while parsing the SVG data.
15
16
  attr_reader :warnings
@@ -18,7 +19,8 @@ class Prawn::Svg::Document
18
19
  attr_accessor :scale
19
20
 
20
21
  attr_reader :root,
21
- :actual_width, :actual_height, :width, :height, :x_offset, :y_offset, :cache_images,
22
+ :actual_width, :actual_height, :width, :height, :x_offset, :y_offset,
23
+ :cache_images, :fallback_font_name,
22
24
  :css_parser, :elements_by_id
23
25
 
24
26
  def initialize(data, bounds, options)
@@ -29,6 +31,7 @@ class Prawn::Svg::Document
29
31
  @options = options
30
32
  @elements_by_id = {}
31
33
  @cache_images = options[:cache_images]
34
+ @fallback_font_name = options.fetch(:fallback_font_name, DEFAULT_FALLBACK_FONT_NAME)
32
35
  @actual_width, @actual_height = bounds # set this first so % width/heights can be used
33
36
 
34
37
  if vb = @root.attributes['viewBox']
@@ -51,41 +51,54 @@ class Prawn::Svg::Element
51
51
 
52
52
  protected
53
53
  def apply_styles
54
- # Transform
55
- if transform = @attributes['transform']
56
- parse_css_method_calls(transform).each do |name, arguments|
57
- case name
58
- when 'translate'
59
- x, y = arguments
60
- add_call_and_enter name, @document.distance(x), -@document.distance(y)
61
-
62
- when 'rotate'
63
- r, x, y = arguments.collect {|a| a.to_f}
64
- if arguments.length == 3
65
- add_call_and_enter name, -r, :origin => [@document.x(x), @document.y(y)]
66
- else
67
- add_call_and_enter name, -r, :origin => [0, @document.y('0')]
68
- end
69
-
70
- when 'scale'
71
- x_scale = arguments[0].to_f
72
- y_scale = (arguments[1] || x_scale).to_f
73
- add_call_and_enter "transformation_matrix", x_scale, 0, 0, y_scale, 0, 0
74
-
75
- when 'matrix'
76
- if arguments.length != 6
77
- @document.warnings << "transform 'matrix' must have six arguments"
78
- else
79
- a, b, c, d, e, f = arguments.collect {|argument| argument.to_f}
80
- add_call_and_enter "transformation_matrix", a, -b, -c, d, @document.distance(e), -@document.distance(f)
81
- end
54
+ parse_transform_attribute_and_call
55
+ parse_opacity_attributes_and_call
56
+ parse_clip_path_attribute_and_call
57
+ draw_types = parse_fill_and_stroke_attributes_and_call
58
+ parse_stroke_width_attribute_and_call
59
+ parse_font_attributes_and_call
60
+
61
+ if draw_types.length > 0 && !@state[:disable_drawing] && !Prawn::Svg::Parser::CONTAINER_TAGS.include?(element.name)
62
+ add_call_and_enter(draw_types.join("_and_"))
63
+ end
64
+ end
65
+
66
+ def parse_transform_attribute_and_call
67
+ return unless transform = @attributes['transform']
68
+
69
+ parse_css_method_calls(transform).each do |name, arguments|
70
+ case name
71
+ when 'translate'
72
+ x, y = arguments
73
+ add_call_and_enter name, @document.distance(x), -@document.distance(y)
74
+
75
+ when 'rotate'
76
+ r, x, y = arguments.collect {|a| a.to_f}
77
+ if arguments.length == 3
78
+ add_call_and_enter name, -r, :origin => [@document.x(x), @document.y(y)]
79
+ else
80
+ add_call_and_enter name, -r, :origin => [0, @document.y('0')]
81
+ end
82
+
83
+ when 'scale'
84
+ x_scale = arguments[0].to_f
85
+ y_scale = (arguments[1] || x_scale).to_f
86
+ add_call_and_enter "transformation_matrix", x_scale, 0, 0, y_scale, 0, 0
87
+
88
+ when 'matrix'
89
+ if arguments.length != 6
90
+ @document.warnings << "transform 'matrix' must have six arguments"
82
91
  else
83
- @document.warnings << "Unknown transformation '#{name}'; ignoring"
92
+ a, b, c, d, e, f = arguments.collect {|argument| argument.to_f}
93
+ add_call_and_enter "transformation_matrix", a, -b, -c, d, @document.distance(e), -@document.distance(f)
84
94
  end
95
+ else
96
+ @document.warnings << "Unknown transformation '#{name}'; ignoring"
85
97
  end
86
98
  end
99
+ end
87
100
 
88
- # Opacity:
101
+ def parse_opacity_attributes_and_call
89
102
  # We can't do nested opacities quite like the SVG requires, but this is close enough.
90
103
  fill_opacity = stroke_opacity = clamp(@attributes['opacity'].to_f, 0, 1) if @attributes['opacity']
91
104
  fill_opacity = clamp(@attributes['fill-opacity'].to_f, 0, 1) if @attributes['fill-opacity']
@@ -97,23 +110,25 @@ class Prawn::Svg::Element
97
110
 
98
111
  add_call_and_enter 'transparent', state[:fill_opacity], state[:stroke_opacity]
99
112
  end
113
+ end
100
114
 
101
- # Clip path
102
- if clip_path = @attributes['clip-path']
103
- if (matches = clip_path.strip.match(/\Aurl\(#(.*)\)\z/)).nil?
104
- document.warnings << "Only clip-path attributes with the form 'url(#xxx)' are supported"
105
- elsif (clip_path_element = @document.elements_by_id[matches[1]]).nil?
106
- document.warnings << "clip-path ID '#{matches[1]}' not defined"
107
- elsif clip_path_element.element.name != "clipPath"
108
- document.warnings << "clip-path ID '#{matches[1]}' does not point to a clipPath tag"
109
- else
110
- add_call_and_enter 'save_graphics_state'
111
- add_calls_from_element clip_path_element
112
- add_call "clip"
113
- end
115
+ def parse_clip_path_attribute_and_call
116
+ return unless clip_path = @attributes['clip-path']
117
+
118
+ if (matches = clip_path.strip.match(/\Aurl\(#(.*)\)\z/)).nil?
119
+ document.warnings << "Only clip-path attributes with the form 'url(#xxx)' are supported"
120
+ elsif (clip_path_element = @document.elements_by_id[matches[1]]).nil?
121
+ document.warnings << "clip-path ID '#{matches[1]}' not defined"
122
+ elsif clip_path_element.element.name != "clipPath"
123
+ document.warnings << "clip-path ID '#{matches[1]}' does not point to a clipPath tag"
124
+ else
125
+ add_call_and_enter 'save_graphics_state'
126
+ add_calls_from_element clip_path_element
127
+ add_call "clip"
114
128
  end
129
+ end
115
130
 
116
- # Fill and stroke
131
+ def parse_fill_and_stroke_attributes_and_call
117
132
  draw_types = []
118
133
  [:fill, :stroke].each do |type|
119
134
  dec = @attributes[type.to_s]
@@ -121,24 +136,31 @@ class Prawn::Svg::Element
121
136
  state[type] = false
122
137
  elsif dec
123
138
  state[type] = true
124
- if color = color_to_hex(dec)
139
+ if color = Prawn::Svg::Color.color_to_hex(dec)
125
140
  add_call "#{type}_color", color
126
141
  end
127
142
  end
128
143
 
129
144
  draw_types << type.to_s if state[type]
130
145
  end
146
+ draw_types
147
+ end
131
148
 
132
- # Stroke width
149
+ def parse_stroke_width_attribute_and_call
133
150
  add_call('line_width', @document.distance(@attributes['stroke-width'])) if @attributes['stroke-width']
151
+ end
134
152
 
135
- # Fonts
153
+ def parse_font_attributes_and_call
136
154
  if size = @attributes['font-size']
137
155
  @state[:font_size] = size.to_f * @document.scale
138
156
  end
139
157
  if weight = @attributes['font-weight']
140
158
  font_updated = true
141
- @state[:font_style] = weight == 'bold' ? :bold : nil
159
+ @state[:font_weight] = Prawn::Svg::Font.weight_for_css_font_weight(weight)
160
+ end
161
+ if style = @attributes['font-style']
162
+ font_updated = true
163
+ @state[:font_style] = style == 'italic' ? :italic : nil
142
164
  end
143
165
  if (family = @attributes['font-family']) && family.strip != ""
144
166
  font_updated = true
@@ -146,20 +168,23 @@ class Prawn::Svg::Element
146
168
  end
147
169
 
148
170
  if @state[:font_family] && font_updated
149
- if pdf_font = Prawn::Svg::Font.map_font_family_to_pdf_font(@state[:font_family], @state[:font_style])
150
- add_call_and_enter 'font', pdf_font
151
- else
152
- @document.warnings << "Font family '#{@state[:font_family]}' style '#{@state[:font_style] || 'normal'}' is not a known font."
171
+ usable_font_families = [@state[:font_family], document.fallback_font_name]
172
+
173
+ font_used = usable_font_families.compact.detect do |name|
174
+ if font = Prawn::Svg::Font.load(name, @state[:font_weight], @state[:font_style])
175
+ @state[:font_subfamily] = font.subfamily
176
+ add_call_and_enter 'font', font.name, :style => @state[:font_subfamily]
177
+ true
178
+ end
153
179
  end
154
- end
155
180
 
156
- # Call fill, stroke, or both
157
- draw_type = draw_types.join("_and_")
158
- if draw_type != "" && !@state[:disable_drawing] && !Prawn::Svg::Parser::CONTAINER_TAGS.include?(element.name)
159
- add_call_and_enter(draw_type)
181
+ if font_used.nil?
182
+ @document.warnings << "Font family '#{@state[:font_family]}' style '#{@state[:font_style] || 'normal'}' is not a known font, and the fallback font could not be found."
183
+ end
160
184
  end
161
185
  end
162
186
 
187
+
163
188
  def parse_css_method_calls(string)
164
189
  string.scan(/\s*(\w+)\(([^)]+)\)\s*/).collect do |call|
165
190
  name, argument_string = call
@@ -168,35 +193,6 @@ class Prawn::Svg::Element
168
193
  end
169
194
  end
170
195
 
171
- # TODO : use http://www.w3.org/TR/SVG11/types.html#ColorKeywords
172
- HTML_COLORS = {
173
- 'black' => "000000", 'green' => "008000", 'silver' => "c0c0c0", 'lime' => "00ff00",
174
- 'gray' => "808080", 'olive' => "808000", 'white' => "ffffff", 'yellow' => "ffff00",
175
- 'maroon' => "800000", 'navy' => "000080", 'red' => "ff0000", 'blue' => "0000ff",
176
- 'purple' => "800080", 'teal' => "008080", 'fuchsia' => "ff00ff", 'aqua' => "00ffff"
177
- }.freeze
178
-
179
- RGB_VALUE_REGEXP = "\s*(-?[0-9.]+%?)\s*"
180
- RGB_REGEXP = /\Argb\(#{RGB_VALUE_REGEXP},#{RGB_VALUE_REGEXP},#{RGB_VALUE_REGEXP}\)\z/i
181
-
182
- def color_to_hex(color_string)
183
- color_string.scan(/([^(\s]+(\([^)]*\))?)/).detect do |color, *_|
184
- if m = color.match(/\A#([0-9a-f])([0-9a-f])([0-9a-f])\z/i)
185
- break "#{m[1] * 2}#{m[2] * 2}#{m[3] * 2}"
186
- elsif color.match(/\A#[0-9a-f]{6}\z/i)
187
- break color[1..6]
188
- elsif hex = HTML_COLORS[color.downcase]
189
- break hex
190
- elsif m = color.match(RGB_REGEXP)
191
- break (1..3).collect do |n|
192
- value = m[n].to_f
193
- value *= 2.55 if m[n][-1..-1] == '%'
194
- "%02x" % clamp(value.round, 0, 255)
195
- end.join
196
- end
197
- end
198
- end
199
-
200
196
  def clamp(value, min_value, max_value)
201
197
  [[value, min_value].max, max_value].min
202
198
  end
@@ -1,6 +1,4 @@
1
1
  class Prawn::Svg::Font
2
- BUILT_IN_FONTS = ["Courier", "Helvetica", "Times-Roman", "Symbol", "ZapfDingbats"]
3
-
4
2
  GENERIC_CSS_FONT_MAPPING = {
5
3
  "serif" => "Times-Roman",
6
4
  "sans-serif" => "Helvetica",
@@ -8,50 +6,96 @@ class Prawn::Svg::Font
8
6
  "fantasy" => "Times-Roman",
9
7
  "monospace" => "Courier"}
10
8
 
11
- def self.map_font_family_to_pdf_font(font_family, font_style = nil)
12
- font_family.split(",").detect do |font|
13
- font = font.gsub(/['"]/, '').gsub(/\s{2,}/, ' ').strip.downcase
9
+ attr_reader :name, :weight, :style
14
10
 
15
- built_in_font = BUILT_IN_FONTS.detect {|f| f.downcase == font}
16
- break built_in_font if built_in_font
11
+ def self.load(family, weight = nil, style = nil)
12
+ family.split(",").detect do |name|
13
+ name = name.gsub(/['"]/, '').gsub(/\s{2,}/, ' ').strip.downcase
17
14
 
18
- generic_font = GENERIC_CSS_FONT_MAPPING[font]
19
- break generic_font if generic_font
15
+ # If it's a standard CSS font name, map it to one of the standard PDF fonts.
16
+ name = GENERIC_CSS_FONT_MAPPING[name] || name
20
17
 
21
- break font.downcase if font_installed?(font, font_style)
18
+ font = new(name, weight, style)
19
+ break font if font.installed?
22
20
  end
23
21
  end
24
22
 
25
- def self.font_path(font_family, font_style = nil)
26
- font_style = :normal if font_style.nil?
27
- if installed_styles = installed_fonts[font_family.downcase]
28
- installed_styles[font_style]
23
+ def self.weight_for_css_font_weight(weight)
24
+ case weight
25
+ when '100', '200', '300' then :light
26
+ when '400', '500' then :normal
27
+ when '600' then :semibold
28
+ when '700', 'bold' then :bold
29
+ when '800' then :extrabold
30
+ when '900' then :black
29
31
  end
30
32
  end
31
33
 
32
- def self.font_installed?(font_family, font_style = nil)
33
- !font_path(font_family, font_style).nil?
34
+ # This method is passed prawn's font_families hash. It'll be pre-populated with the fonts that prawn natively
35
+ # supports. We'll add fonts we find in the font path to this hash.
36
+ def self.load_external_fonts(fonts)
37
+ Prawn::Svg::Interface.font_path.uniq.collect {|path| Dir["#{path}/*"]}.flatten.each do |filename|
38
+ information = font_information(filename) rescue nil
39
+ if information && font_name = (information[16] || information[1])
40
+ subfamily = (information[17] || information[2]).gsub(/\s+/, "_").downcase.to_sym
41
+ subfamily = :normal if subfamily == :regular
42
+ (fonts[font_name] ||= {})[subfamily] = filename
43
+ end
44
+ end
45
+
46
+ @font_case_mapping = {}
47
+ fonts.each {|key, _| @font_case_mapping[key.downcase] = key}
48
+
49
+ @installed_fonts = fonts
34
50
  end
35
51
 
36
52
  def self.installed_fonts
37
- return @installed_fonts if @installed_fonts
53
+ @installed_fonts
54
+ end
38
55
 
39
- fonts = {}
40
- Prawn::Svg::Interface.font_path.uniq.collect {|path| Dir["#{path}/*"]}.flatten.each do |filename|
41
- information = font_information(filename) rescue nil
42
- if information && font_name = information[1]
43
- font_style = case information[2]
44
- when 'Bold' then :bold
45
- when 'Italic' then :italic
46
- when 'Bold Italic' then :bold_italic
47
- else :normal
48
- end
56
+ def self.correctly_cased_font_name(name)
57
+ @font_case_mapping[name.downcase]
58
+ end
59
+
60
+
61
+ def initialize(name, weight, style)
62
+ @name = self.class.correctly_cased_font_name(name) || name
63
+ @weight = weight
64
+ @style = style
65
+ end
66
+
67
+ def installed?
68
+ subfamilies = self.class.installed_fonts[name]
69
+ !subfamilies.nil? && subfamilies.key?(subfamily)
70
+ end
49
71
 
50
- (fonts[font_name.downcase] ||= {})[font_style] = filename
72
+ # Construct a subfamily name, ensuring that the subfamily is a valid one for the font.
73
+ def subfamily
74
+ if subfamilies = self.class.installed_fonts[name]
75
+ if subfamilies.key?(subfamily_name)
76
+ subfamily_name
77
+ elsif subfamilies.key?(:normal)
78
+ :normal
79
+ else
80
+ subfamilies.keys.first
51
81
  end
52
82
  end
83
+ end
53
84
 
54
- @installed_fonts = fonts
85
+
86
+ private
87
+ # Construct a subfamily name from the weight and style information.
88
+ # Note that this name might not actually exist in the font.
89
+ def subfamily_name
90
+ sfn = if weight == :normal && style
91
+ style
92
+ elsif weight || style
93
+ "#{weight} #{style}"
94
+ else
95
+ "normal"
96
+ end
97
+
98
+ sfn.strip.gsub(/\s+/, "_").downcase.to_sym
55
99
  end
56
100
 
57
101
  def self.font_information(filename)
@@ -78,7 +122,7 @@ class Prawn::Svg::Font
78
122
  start = 6 + index * 12
79
123
  platform_id, platform_specific_id, language_id, name_id, length, offset = data[start..start+11].unpack("nnnnnn")
80
124
  next unless language_id == 0 # English
81
- next unless name_id == 1 || name_id == 2
125
+ next unless [1, 2, 16, 17].include?(name_id)
82
126
 
83
127
  offset += string_offset
84
128
  field = data[offset..offset+length-1]
@@ -5,7 +5,7 @@
5
5
  module Prawn
6
6
  module Svg
7
7
  class Interface
8
- DEFAULT_FONT_PATHS = ["/Library/Fonts", "/usr/share/fonts/truetype/**"]
8
+ DEFAULT_FONT_PATHS = ["/Library/Fonts", "/System/Library/Fonts", "#{ENV["HOME"]}/Library/Fonts", "/usr/share/fonts/truetype/**"]
9
9
 
10
10
  @font_path = []
11
11
  DEFAULT_FONT_PATHS.each {|path| @font_path << path if File.exists?(path)}
@@ -31,7 +31,7 @@ module Prawn
31
31
 
32
32
  @options[:at] or raise "options[:at] must be specified"
33
33
 
34
- prawn.font_families.update(Prawn::Svg::Font.installed_fonts)
34
+ Prawn::Svg::Font.load_external_fonts(prawn.font_families)
35
35
 
36
36
  @document = Document.new(data, [prawn.bounds.width, prawn.bounds.height], options)
37
37
  end
@@ -95,7 +95,7 @@ module Prawn
95
95
  x = prawn.bounds.absolute_left
96
96
  y = prawn.bounds.absolute_top
97
97
  arguments[4] += x - (x * arguments[0] - y * arguments[1])
98
- arguments[5] += y - (x * arguments[1] + y * arguments[0])
98
+ arguments[5] += y - (x * arguments[2] + y * arguments[3])
99
99
 
100
100
  when 'clip'
101
101
  prawn.add_content "W n" # clip to path
@@ -22,9 +22,7 @@ class Prawn::Svg::Parser::Text
22
22
  if size = element.state[:font_size]
23
23
  opts[:size] = size
24
24
  end
25
- if style = element.state[:font_style]
26
- opts[:style] = style
27
- end
25
+ opts[:style] = element.state[:font_subfamily]
28
26
 
29
27
  # This is not a prawn option but we can't work out how to render it here -
30
28
  # it's handled by Svg#rewrite_call_arguments
@@ -1,5 +1,5 @@
1
1
  module Prawn
2
2
  module Svg
3
- VERSION = '0.12.0.4'
3
+ VERSION = '0.12.0.6'
4
4
  end
5
5
  end
data/prawn-svg.gemspec CHANGED
@@ -10,13 +10,14 @@ spec = Gem::Specification.new do |gem|
10
10
  gem.author = "Roger Nesbitt"
11
11
  gem.email = "roger@seriousorange.com"
12
12
  gem.homepage = "http://github.com/mogest/prawn-svg"
13
-
13
+ gem.license = 'MIT'
14
+
14
15
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
15
16
  gem.files = `git ls-files`.split("\n")
16
17
  gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
18
  gem.name = "prawn-svg"
18
19
  gem.require_paths = ["lib"]
19
-
20
+
20
21
  gem.add_dependency "prawn", ">= 0.8.4"
21
22
  gem.add_development_dependency "rspec"
22
23
  gem.add_development_dependency "rake"
@@ -0,0 +1,26 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Prawn::Svg::Color do
4
+ describe :color_to_hex do
5
+ it "converts #xxx to a hex value" do
6
+ Prawn::Svg::Color.color_to_hex("#9ab").should == "99aabb"
7
+ end
8
+
9
+ it "converts #xxxxxx to a hex value" do
10
+ Prawn::Svg::Color.color_to_hex("#9ab123").should == "9ab123"
11
+ end
12
+
13
+ it "converts an html colour name to a hex value" do
14
+ Prawn::Svg::Color.color_to_hex("White").should == "ffffff"
15
+ end
16
+
17
+ it "converts an rgb string to a hex value" do
18
+ Prawn::Svg::Color.color_to_hex("rgb(16, 32, 48)").should == "102030"
19
+ Prawn::Svg::Color.color_to_hex("rgb(-5, 50%, 120%)").should == "007fff"
20
+ end
21
+
22
+ it "scans the string and finds the first colour it can parse" do
23
+ Prawn::Svg::Color.color_to_hex("function(#someurl, 0) nonexistent rgb( 3 ,4,5 ) white").should == "030405"
24
+ end
25
+ end
26
+ end
@@ -4,29 +4,34 @@ describe Prawn::Svg::Element do
4
4
  before :each do
5
5
  e = mock
6
6
  e.stub!(:attributes).and_return({})
7
- @element = Prawn::Svg::Element.new(nil, e, [], {})
7
+
8
+ @document = Struct.new(:fallback_font_name, :css_parser, :warnings).new("Courier", nil, [])
9
+ @element = Prawn::Svg::Element.new(@document, e, [], {})
8
10
  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
11
 
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"
12
+ describe :parse_font_attributes_and_call do
13
+ it "uses a font if it can find it" do
14
+ @element.should_receive(:add_call_and_enter).with('font', 'Helvetica', :style => :normal)
15
+
16
+ @element.attributes["font-family"] = "Helvetica"
17
+ @element.send :parse_font_attributes_and_call
21
18
  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"
19
+
20
+ it "uses the fallback font if the requested font is not defined" do
21
+ @element.should_receive(:add_call_and_enter).with('font', 'Courier', :style => :normal)
22
+
23
+ @element.attributes["font-family"] = "Font That Doesn't Exist"
24
+ @element.send :parse_font_attributes_and_call
26
25
  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"
26
+
27
+ it "doesn't call the font method if there's no fallback font" do
28
+ @document.fallback_font_name = nil
29
+
30
+ @element.should_not_receive(:add_call_and_enter)
31
+
32
+ @element.attributes["font-family"] = "Font That Doesn't Exist"
33
+ @element.send :parse_font_attributes_and_call
34
+ @document.warnings.length.should == 1
30
35
  end
31
36
  end
32
37
  end
@@ -1,31 +1,31 @@
1
1
  require File.dirname(__FILE__) + '/../../spec_helper'
2
2
 
3
3
  describe Prawn::Svg::Font do
4
- describe :map_font_family_to_pdf_font do
4
+ describe :load do
5
5
  it "matches a built in font" do
6
- Prawn::Svg::Font.map_font_family_to_pdf_font("blah, 'courier', nothing").should == 'Courier'
6
+ Prawn::Svg::Font.load("blah, 'courier', nothing").name.should == 'Courier'
7
7
  end
8
-
8
+
9
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'
10
+ Prawn::Svg::Font.load("serif").name.should == 'Times-Roman'
11
+ Prawn::Svg::Font.load("blah, serif").name.should == 'Times-Roman'
12
+ Prawn::Svg::Font.load("blah, serif , test").name.should == 'Times-Roman'
13
13
  end
14
-
15
- if !Prawn::Svg::Font.installed_fonts.empty?
14
+
15
+ if Prawn::Svg::Font.installed_fonts["Verdana"]
16
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"
17
+ Prawn::Svg::Font.load("verdana, sans-serif").name.should == 'Verdana'
18
+ Prawn::Svg::Font.load("VERDANA, sans-serif").name.should == 'Verdana'
19
+ Prawn::Svg::Font.load("something, \"Times New Roman\", serif").name.should == "Times New Roman"
20
+ Prawn::Svg::Font.load("something, Times New Roman, serif").name.should == "Times New Roman"
21
21
  end
22
22
  else
23
- it "not running font test because we couldn't find a font directory"
23
+ it "not running font test because we couldn't find Verdana installed on the system"
24
24
  end
25
-
25
+
26
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
27
+ Prawn::Svg::Font.load("blah, thing").should be_nil
28
+ Prawn::Svg::Font.load("").should be_nil
29
29
  end
30
30
  end
31
31
  end
@@ -0,0 +1,12 @@
1
+ <svg style="overflow: hidden; position: relative;" height="579.1447861965491" version="1.1" width="772" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
2
+ <rect x="0" y="0" width="772" height="19" r="0" rx="0" ry="0" fill="#000000" stroke="#000000" transform="matrix(1,0,0,1,0,0.5)" stroke-width="1"></rect>
3
+ <rect x="0" y="560" width="772" height="19" r="0" rx="0" ry="0" fill="#000000" stroke="#000000" transform="matrix(1,0,0,1,0,0.5)" stroke-width="1"></rect>
4
+ <rect x="0" y="0" width="19" height="579" r="0" rx="0" ry="0" fill="#000000" stroke="#000000" transform="matrix(1,0,0,1,0,0.5)" stroke-width="1"></rect>
5
+ <rect x="753" y="0" width="19" height="579" r="0" rx="0" ry="0" fill="#000000" stroke="#000000" transform="matrix(1,0,0,1,0,0.5)" stroke-width="1"></rect>
6
+ <g style="text-anchor: middle; font-style: normal; font-variant: normal; font-size: 80px; line-height: normal; font-family: 'Open Sans';" transform="translate(186, 151)">
7
+ <text y="0" fill="black" style="font-weight: 100;">prawn-svg</text>
8
+ <text y="100" fill="black" style="font-weight: 400;">prawn-svg</text>
9
+ <text y="200" fill="black" style="font-weight: 600;">prawn-svg</text>
10
+ <text y="300" fill="black" style="font-weight: 800;">prawn-svg</text>
11
+ </g>
12
+ </svg>
data/spec/spec_helper.rb CHANGED
@@ -7,3 +7,5 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
7
7
 
8
8
  RSpec.configure do |config|
9
9
  end
10
+
11
+ Prawn::Svg::Font.load_external_fonts(Prawn::Document.new.font_families)
metadata CHANGED
@@ -1,62 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prawn-svg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0.4
5
- prerelease:
4
+ version: 0.12.0.6
6
5
  platform: ruby
7
6
  authors:
8
7
  - Roger Nesbitt
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-04-03 00:00:00.000000000 Z
11
+ date: 2013-08-10 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: prawn
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - '>='
20
18
  - !ruby/object:Gem::Version
21
19
  version: 0.8.4
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - '>='
28
25
  - !ruby/object:Gem::Version
29
26
  version: 0.8.4
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: rspec
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - '>='
36
32
  - !ruby/object:Gem::Version
37
33
  version: '0'
38
34
  type: :development
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - '>='
44
39
  - !ruby/object:Gem::Version
45
40
  version: '0'
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: rake
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ! '>='
45
+ - - '>='
52
46
  - !ruby/object:Gem::Version
53
47
  version: '0'
54
48
  type: :development
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
- - - ! '>='
52
+ - - '>='
60
53
  - !ruby/object:Gem::Version
61
54
  version: '0'
62
55
  description: SVG renderer for Prawn PDF library
@@ -72,6 +65,7 @@ files:
72
65
  - README.md
73
66
  - Rakefile
74
67
  - lib/prawn-svg.rb
68
+ - lib/prawn/svg/color.rb
75
69
  - lib/prawn/svg/document.rb
76
70
  - lib/prawn/svg/element.rb
77
71
  - lib/prawn/svg/extension.rb
@@ -86,6 +80,7 @@ files:
86
80
  - spec/lib/parser_spec.rb
87
81
  - spec/lib/path_spec.rb
88
82
  - spec/lib/svg_spec.rb
83
+ - spec/prawn/svg/color_spec.rb
89
84
  - spec/prawn/svg/document_spec.rb
90
85
  - spec/prawn/svg/element_spec.rb
91
86
  - spec/prawn/svg/font_spec.rb
@@ -113,6 +108,7 @@ files:
113
108
  - spec/sample_svg/rect02.svg
114
109
  - spec/sample_svg/rotate_scale.svg
115
110
  - spec/sample_svg/scruffy_graph.svg
111
+ - spec/sample_svg/subfamilies.svg
116
112
  - spec/sample_svg/text_entities.svg
117
113
  - spec/sample_svg/triangle01.svg
118
114
  - spec/sample_svg/tspan01.svg
@@ -122,33 +118,34 @@ files:
122
118
  - spec/sample_svg/use.svg
123
119
  - spec/spec_helper.rb
124
120
  homepage: http://github.com/mogest/prawn-svg
125
- licenses: []
121
+ licenses:
122
+ - MIT
123
+ metadata: {}
126
124
  post_install_message:
127
125
  rdoc_options: []
128
126
  require_paths:
129
127
  - lib
130
128
  required_ruby_version: !ruby/object:Gem::Requirement
131
- none: false
132
129
  requirements:
133
- - - ! '>='
130
+ - - '>='
134
131
  - !ruby/object:Gem::Version
135
132
  version: '0'
136
133
  required_rubygems_version: !ruby/object:Gem::Requirement
137
- none: false
138
134
  requirements:
139
- - - ! '>='
135
+ - - '>='
140
136
  - !ruby/object:Gem::Version
141
137
  version: '0'
142
138
  requirements: []
143
139
  rubyforge_project:
144
- rubygems_version: 1.8.23
140
+ rubygems_version: 2.0.0
145
141
  signing_key:
146
- specification_version: 3
142
+ specification_version: 4
147
143
  summary: SVG renderer for Prawn PDF library
148
144
  test_files:
149
145
  - spec/lib/parser_spec.rb
150
146
  - spec/lib/path_spec.rb
151
147
  - spec/lib/svg_spec.rb
148
+ - spec/prawn/svg/color_spec.rb
152
149
  - spec/prawn/svg/document_spec.rb
153
150
  - spec/prawn/svg/element_spec.rb
154
151
  - spec/prawn/svg/font_spec.rb
@@ -176,6 +173,7 @@ test_files:
176
173
  - spec/sample_svg/rect02.svg
177
174
  - spec/sample_svg/rotate_scale.svg
178
175
  - spec/sample_svg/scruffy_graph.svg
176
+ - spec/sample_svg/subfamilies.svg
179
177
  - spec/sample_svg/text_entities.svg
180
178
  - spec/sample_svg/triangle01.svg
181
179
  - spec/sample_svg/tspan01.svg