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 +7 -0
- data/.gitignore +1 -0
- data/LICENSE +1 -1
- data/README.md +34 -33
- data/lib/prawn-svg.rb +1 -0
- data/lib/prawn/svg/color.rb +35 -0
- data/lib/prawn/svg/document.rb +4 -1
- data/lib/prawn/svg/element.rb +82 -86
- data/lib/prawn/svg/font.rb +74 -30
- data/lib/prawn/svg/interface.rb +3 -3
- data/lib/prawn/svg/parser/text.rb +1 -3
- data/lib/prawn/svg/version.rb +1 -1
- data/prawn-svg.gemspec +3 -2
- data/spec/prawn/svg/color_spec.rb +26 -0
- data/spec/prawn/svg/element_spec.rb +24 -19
- data/spec/prawn/svg/font_spec.rb +16 -16
- data/spec/sample_svg/subfamilies.svg +12 -0
- data/spec/spec_helper.rb +2 -0
- metadata +20 -22
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
data/LICENSE
CHANGED
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
@@ -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
|
data/lib/prawn/svg/document.rb
CHANGED
@@ -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,
|
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']
|
data/lib/prawn/svg/element.rb
CHANGED
@@ -51,41 +51,54 @@ class Prawn::Svg::Element
|
|
51
51
|
|
52
52
|
protected
|
53
53
|
def apply_styles
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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[:
|
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
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
-
|
157
|
-
|
158
|
-
|
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
|
data/lib/prawn/svg/font.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
16
|
-
|
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
|
-
|
19
|
-
|
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
|
-
|
18
|
+
font = new(name, weight, style)
|
19
|
+
break font if font.installed?
|
22
20
|
end
|
23
21
|
end
|
24
22
|
|
25
|
-
def self.
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
33
|
-
|
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
|
-
|
53
|
+
@installed_fonts
|
54
|
+
end
|
38
55
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
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
|
-
|
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
|
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]
|
data/lib/prawn/svg/interface.rb
CHANGED
@@ -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
|
-
|
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[
|
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
|
-
|
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
|
data/lib/prawn/svg/version.rb
CHANGED
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
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
@element.send
|
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 "
|
24
|
-
@element.
|
25
|
-
|
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 "
|
29
|
-
@
|
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
|
data/spec/prawn/svg/font_spec.rb
CHANGED
@@ -1,31 +1,31 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
2
|
|
3
3
|
describe Prawn::Svg::Font do
|
4
|
-
describe :
|
4
|
+
describe :load do
|
5
5
|
it "matches a built in font" do
|
6
|
-
Prawn::Svg::Font.
|
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.
|
11
|
-
Prawn::Svg::Font.
|
12
|
-
Prawn::Svg::Font.
|
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
|
14
|
+
|
15
|
+
if Prawn::Svg::Font.installed_fonts["Verdana"]
|
16
16
|
it "matches a font installed on the system" do
|
17
|
-
Prawn::Svg::Font.
|
18
|
-
Prawn::Svg::Font.
|
19
|
-
Prawn::Svg::Font.
|
20
|
-
Prawn::Svg::Font.
|
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
|
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.
|
28
|
-
Prawn::Svg::Font.
|
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
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.
|
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-
|
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:
|
140
|
+
rubygems_version: 2.0.0
|
145
141
|
signing_key:
|
146
|
-
specification_version:
|
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
|