prawn-svg 0.12.0.4 → 0.12.0.6
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.
- 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
|