prawn-svg 0.21.0 → 0.22.1
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 +4 -4
- data/.travis.yml +6 -0
- data/README.md +11 -4
- data/lib/prawn-svg.rb +9 -6
- data/lib/prawn/svg/attributes.rb +6 -0
- data/lib/prawn/svg/attributes/clip_path.rb +17 -0
- data/lib/prawn/svg/attributes/display.rb +5 -0
- data/lib/prawn/svg/attributes/font.rb +38 -0
- data/lib/prawn/svg/attributes/opacity.rb +15 -0
- data/lib/prawn/svg/attributes/stroke.rb +35 -0
- data/lib/prawn/svg/attributes/transform.rb +50 -0
- data/lib/prawn/svg/calculators/aspect_ratio.rb +1 -1
- data/lib/prawn/svg/calculators/document_sizing.rb +2 -2
- data/lib/prawn/svg/calculators/pixels.rb +1 -1
- data/lib/prawn/svg/color.rb +44 -14
- data/lib/prawn/svg/document.rb +6 -5
- data/lib/prawn/svg/elements.rb +33 -0
- data/lib/prawn/svg/elements/base.rb +228 -0
- data/lib/prawn/svg/elements/circle.rb +25 -0
- data/lib/prawn/svg/elements/container.rb +15 -0
- data/lib/prawn/svg/elements/ellipse.rb +23 -0
- data/lib/prawn/svg/elements/gradient.rb +117 -0
- data/lib/prawn/svg/elements/ignored.rb +5 -0
- data/lib/prawn/svg/elements/image.rb +85 -0
- data/lib/prawn/svg/elements/line.rb +16 -0
- data/lib/prawn/svg/elements/path.rb +405 -0
- data/lib/prawn/svg/elements/polygon.rb +17 -0
- data/lib/prawn/svg/elements/polyline.rb +22 -0
- data/lib/prawn/svg/elements/rect.rb +33 -0
- data/lib/prawn/svg/elements/root.rb +9 -0
- data/lib/prawn/svg/elements/style.rb +10 -0
- data/lib/prawn/svg/elements/text.rb +87 -0
- data/lib/prawn/svg/elements/use.rb +29 -0
- data/lib/prawn/svg/extension.rb +2 -2
- data/lib/prawn/svg/font.rb +3 -3
- data/lib/prawn/svg/interface.rb +12 -5
- data/lib/prawn/svg/url_loader.rb +1 -1
- data/lib/prawn/svg/version.rb +2 -2
- data/prawn-svg.gemspec +3 -3
- data/spec/integration_spec.rb +59 -2
- data/spec/prawn/svg/attributes/font_spec.rb +49 -0
- data/spec/prawn/svg/attributes/transform_spec.rb +56 -0
- data/spec/prawn/svg/calculators/aspect_ratio_spec.rb +2 -2
- data/spec/prawn/svg/calculators/document_sizing_spec.rb +3 -3
- data/spec/prawn/svg/color_spec.rb +36 -15
- data/spec/prawn/svg/document_spec.rb +4 -4
- data/spec/prawn/svg/elements/base_spec.rb +125 -0
- data/spec/prawn/svg/elements/gradient_spec.rb +61 -0
- data/spec/prawn/svg/elements/path_spec.rb +123 -0
- data/spec/prawn/svg/elements/style_spec.rb +23 -0
- data/spec/prawn/svg/{parser → elements}/text_spec.rb +7 -8
- data/spec/prawn/svg/font_spec.rb +12 -12
- data/spec/prawn/svg/interface_spec.rb +7 -7
- data/spec/prawn/svg/url_loader_spec.rb +2 -2
- data/spec/sample_svg/gradients.svg +40 -0
- data/spec/sample_svg/rect02.svg +8 -11
- data/spec/spec_helper.rb +1 -1
- metadata +46 -18
- data/lib/prawn/svg/element.rb +0 -304
- data/lib/prawn/svg/parser.rb +0 -268
- data/lib/prawn/svg/parser/image.rb +0 -81
- data/lib/prawn/svg/parser/path.rb +0 -392
- data/lib/prawn/svg/parser/text.rb +0 -80
- data/spec/prawn/svg/element_spec.rb +0 -127
- data/spec/prawn/svg/parser/path_spec.rb +0 -89
- data/spec/prawn/svg/parser_spec.rb +0 -55
@@ -0,0 +1,228 @@
|
|
1
|
+
class Prawn::SVG::Elements::Base
|
2
|
+
extend Forwardable
|
3
|
+
|
4
|
+
include Prawn::SVG::Attributes::Transform
|
5
|
+
include Prawn::SVG::Attributes::Opacity
|
6
|
+
include Prawn::SVG::Attributes::ClipPath
|
7
|
+
include Prawn::SVG::Attributes::Stroke
|
8
|
+
include Prawn::SVG::Attributes::Font
|
9
|
+
include Prawn::SVG::Attributes::Display
|
10
|
+
|
11
|
+
COMMA_WSP_REGEXP = Prawn::SVG::Elements::COMMA_WSP_REGEXP
|
12
|
+
|
13
|
+
SkipElementQuietly = Class.new(StandardError)
|
14
|
+
SkipElementError = Class.new(StandardError)
|
15
|
+
MissingAttributesError = Class.new(SkipElementError)
|
16
|
+
|
17
|
+
attr_reader :document, :source, :parent_calls, :base_calls, :state, :attributes
|
18
|
+
attr_accessor :calls
|
19
|
+
|
20
|
+
def_delegators :@document, :x, :y, :distance, :points, :warnings
|
21
|
+
|
22
|
+
def initialize(document, source, parent_calls, state)
|
23
|
+
@document = document
|
24
|
+
@source = source
|
25
|
+
@parent_calls = parent_calls
|
26
|
+
@state = state
|
27
|
+
@base_calls = @calls = []
|
28
|
+
|
29
|
+
if id = source.attributes["id"]
|
30
|
+
document.elements_by_id[id] = self
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def process
|
35
|
+
combine_attributes_and_style_declarations
|
36
|
+
parse
|
37
|
+
|
38
|
+
apply_calls_from_standard_attributes
|
39
|
+
apply
|
40
|
+
|
41
|
+
append_calls_to_parent
|
42
|
+
rescue SkipElementQuietly
|
43
|
+
rescue SkipElementError => e
|
44
|
+
@document.warnings << e.message
|
45
|
+
end
|
46
|
+
|
47
|
+
def name
|
48
|
+
@name ||= source.name
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
def parse
|
54
|
+
end
|
55
|
+
|
56
|
+
def apply
|
57
|
+
end
|
58
|
+
|
59
|
+
def bounding_box
|
60
|
+
end
|
61
|
+
|
62
|
+
def container?
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_call(name, *arguments)
|
67
|
+
@calls << [name.to_s, arguments, []]
|
68
|
+
end
|
69
|
+
|
70
|
+
def add_call_and_enter(name, *arguments)
|
71
|
+
@calls << [name.to_s, arguments, []]
|
72
|
+
@calls = @calls.last.last
|
73
|
+
end
|
74
|
+
|
75
|
+
def append_calls_to_parent
|
76
|
+
@parent_calls.concat(@base_calls)
|
77
|
+
end
|
78
|
+
|
79
|
+
def add_calls_from_element(other)
|
80
|
+
@calls.concat other.base_calls
|
81
|
+
end
|
82
|
+
|
83
|
+
def process_child_elements
|
84
|
+
source.elements.each do |elem|
|
85
|
+
if element_class = Prawn::SVG::Elements::TAG_CLASS_MAPPING[elem.name.to_sym]
|
86
|
+
add_call "save"
|
87
|
+
|
88
|
+
child = element_class.new(@document, elem, @calls, @state.dup)
|
89
|
+
child.process
|
90
|
+
|
91
|
+
add_call "restore"
|
92
|
+
else
|
93
|
+
@document.warnings << "Unknown tag '#{elem.name}'; ignoring"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def apply_calls_from_standard_attributes
|
99
|
+
parse_transform_attribute_and_call
|
100
|
+
parse_opacity_attributes_and_call
|
101
|
+
parse_clip_path_attribute_and_call
|
102
|
+
draw_types = parse_fill_and_stroke_attributes_and_call
|
103
|
+
parse_stroke_attributes_and_call
|
104
|
+
parse_font_attributes_and_call
|
105
|
+
parse_display_attribute
|
106
|
+
apply_drawing_call(draw_types)
|
107
|
+
end
|
108
|
+
|
109
|
+
def apply_drawing_call(draw_types)
|
110
|
+
if !@state[:disable_drawing] && !container?
|
111
|
+
if draw_types.empty? || @state[:display] == "none"
|
112
|
+
add_call_and_enter("end_path")
|
113
|
+
else
|
114
|
+
add_call_and_enter(draw_types.join("_and_"))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def parse_fill_and_stroke_attributes_and_call
|
120
|
+
["fill", "stroke"].select do |type|
|
121
|
+
case keyword = attribute_value_as_keyword(type)
|
122
|
+
when nil
|
123
|
+
when 'inherit'
|
124
|
+
when 'none'
|
125
|
+
state[type.to_sym] = false
|
126
|
+
else
|
127
|
+
state[type.to_sym] = false
|
128
|
+
color_attribute = keyword == 'currentcolor' ? 'color' : type
|
129
|
+
color = @attributes[color_attribute]
|
130
|
+
|
131
|
+
results = Prawn::SVG::Color.parse(color, document.gradients)
|
132
|
+
|
133
|
+
results.each do |result|
|
134
|
+
case result
|
135
|
+
when Prawn::SVG::Color::Hex
|
136
|
+
state[type.to_sym] = true
|
137
|
+
add_call "#{type}_color", result.value
|
138
|
+
break
|
139
|
+
when Prawn::SVG::Elements::Gradient
|
140
|
+
arguments = result.gradient_arguments(self)
|
141
|
+
if arguments
|
142
|
+
state[type.to_sym] = true
|
143
|
+
add_call "#{type}_gradient", **arguments
|
144
|
+
break
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
state[type.to_sym]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def clamp(value, min_value, max_value)
|
155
|
+
[[value, min_value].max, max_value].min
|
156
|
+
end
|
157
|
+
|
158
|
+
def combine_attributes_and_style_declarations
|
159
|
+
if @document && @document.css_parser
|
160
|
+
tag_style = @document.css_parser.find_by_selector(source.name)
|
161
|
+
id_style = @document.css_parser.find_by_selector("##{source.attributes["id"]}") if source.attributes["id"]
|
162
|
+
|
163
|
+
if classes = source.attributes["class"]
|
164
|
+
class_styles = classes.strip.split(/\s+/).collect do |class_name|
|
165
|
+
@document.css_parser.find_by_selector(".#{class_name}")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
element_style = source.attributes['style']
|
170
|
+
|
171
|
+
style = [tag_style, class_styles, id_style, element_style].flatten.collect do |s|
|
172
|
+
s.nil? || s.strip == "" ? "" : "#{s}#{";" unless s.match(/;\s*\z/)}"
|
173
|
+
end.join
|
174
|
+
else
|
175
|
+
style = source.attributes['style'] || ""
|
176
|
+
end
|
177
|
+
|
178
|
+
@attributes = parse_css_declarations(style)
|
179
|
+
|
180
|
+
source.attributes.each do |name, value|
|
181
|
+
name = name.downcase # TODO : this is incorrect; attributes are case sensitive
|
182
|
+
@attributes[name] = value unless @attributes[name]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def parse_css_declarations(declarations)
|
187
|
+
# copied from css_parser
|
188
|
+
declarations.gsub!(/(^[\s]*)|([\s]*$)/, '')
|
189
|
+
|
190
|
+
output = {}
|
191
|
+
declarations.split(/[\;$]+/m).each do |decs|
|
192
|
+
if matches = decs.match(/\s*(.[^:]*)\s*\:\s*(.[^;]*)\s*(;|\Z)/i)
|
193
|
+
property, value, _ = matches.captures
|
194
|
+
output[property.downcase] = value
|
195
|
+
end
|
196
|
+
end
|
197
|
+
output
|
198
|
+
end
|
199
|
+
|
200
|
+
def attribute_value_as_keyword(name)
|
201
|
+
if value = @attributes[name]
|
202
|
+
value.strip.downcase
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def parse_points(points_string)
|
207
|
+
points_string.
|
208
|
+
to_s.
|
209
|
+
strip.
|
210
|
+
gsub(/(\d)-(\d)/, '\1 -\2').
|
211
|
+
split(COMMA_WSP_REGEXP).
|
212
|
+
each_slice(2).
|
213
|
+
map {|x, y| [x(x), y(y)]}
|
214
|
+
end
|
215
|
+
|
216
|
+
def require_attributes(*names)
|
217
|
+
missing_attrs = names - attributes.keys
|
218
|
+
if missing_attrs.any?
|
219
|
+
raise MissingAttributesError, "Must have attributes #{missing_attrs.join(", ")} on tag #{name}; skipping tag"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def require_positive_value(*args)
|
224
|
+
if args.any? {|arg| arg.nil? || arg <= 0}
|
225
|
+
raise SkipElementError, "Invalid attributes on tag #{name}; skipping tag"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Prawn::SVG::Elements::Circle < Prawn::SVG::Elements::Base
|
2
|
+
USE_NEW_CIRCLE_CALL = Prawn::Document.instance_methods.include?(:circle)
|
3
|
+
|
4
|
+
def parse
|
5
|
+
require_attributes 'r'
|
6
|
+
|
7
|
+
@x = x(attributes['cx'] || "0")
|
8
|
+
@y = y(attributes['cy'] || "0")
|
9
|
+
@r = distance(attributes['r'])
|
10
|
+
|
11
|
+
require_positive_value @r
|
12
|
+
end
|
13
|
+
|
14
|
+
def apply
|
15
|
+
if USE_NEW_CIRCLE_CALL
|
16
|
+
add_call "circle", [@x, @y], @r
|
17
|
+
else
|
18
|
+
add_call "circle_at", [@x, @y], radius: @r
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def bounding_box
|
23
|
+
[@x - @r, @y + @r, @x + @r, @y - @r]
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Prawn::SVG::Elements::Container < Prawn::SVG::Elements::Base
|
2
|
+
def parse
|
3
|
+
state[:disable_drawing] = true if name == "clipPath"
|
4
|
+
end
|
5
|
+
|
6
|
+
def apply
|
7
|
+
process_child_elements
|
8
|
+
|
9
|
+
raise SkipElementQuietly if %w(symbol defs clipPath).include?(name)
|
10
|
+
end
|
11
|
+
|
12
|
+
def container?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Prawn::SVG::Elements::Ellipse < Prawn::SVG::Elements::Base
|
2
|
+
USE_NEW_ELLIPSE_CALL = Prawn::Document.instance_methods.include?(:ellipse)
|
3
|
+
|
4
|
+
def parse
|
5
|
+
require_attributes 'rx', 'ry'
|
6
|
+
|
7
|
+
@x = x(attributes['cx'] || "0")
|
8
|
+
@y = y(attributes['cy'] || "0")
|
9
|
+
@rx = distance(attributes['rx'], :x)
|
10
|
+
@ry = distance(attributes['ry'], :y)
|
11
|
+
|
12
|
+
require_positive_value @rx, @ry
|
13
|
+
end
|
14
|
+
|
15
|
+
def apply
|
16
|
+
add_call USE_NEW_ELLIPSE_CALL ? "ellipse" : "ellipse_at", [@x, @y], @rx, @ry
|
17
|
+
end
|
18
|
+
|
19
|
+
def bounding_box
|
20
|
+
[@x - @rx, @y + @ry, @x + @rx, @y - @ry]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,117 @@
|
|
1
|
+
class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
|
2
|
+
TAG_NAME_TO_TYPE = {"linearGradient" => :linear}
|
3
|
+
|
4
|
+
def parse
|
5
|
+
# A gradient tag without an ID is inaccessible and can never be used
|
6
|
+
raise SkipElementQuietly if attributes['id'].nil?
|
7
|
+
|
8
|
+
assert_compatible_prawn_version
|
9
|
+
load_gradient_configuration
|
10
|
+
load_coordinates
|
11
|
+
load_stops
|
12
|
+
|
13
|
+
document.gradients[attributes['id']] = self
|
14
|
+
|
15
|
+
raise SkipElementQuietly # we don't want anything pushed onto the call stack
|
16
|
+
end
|
17
|
+
|
18
|
+
def gradient_arguments(element)
|
19
|
+
case @units
|
20
|
+
when :bounding_box
|
21
|
+
x1, y1, x2, y2 = element.bounding_box
|
22
|
+
return if y2.nil?
|
23
|
+
|
24
|
+
width = x2 - x1
|
25
|
+
height = y1 - y2
|
26
|
+
|
27
|
+
from = [x1 + width * @x1, y1 - height * @y1]
|
28
|
+
to = [x1 + width * @x2, y1 - height * @y2]
|
29
|
+
|
30
|
+
when :user_space
|
31
|
+
from = [@x1, @y1]
|
32
|
+
to = [@x2, @y2]
|
33
|
+
end
|
34
|
+
|
35
|
+
{from: from, to: to, stops: @stops}
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def type
|
41
|
+
TAG_NAME_TO_TYPE.fetch(name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def assert_compatible_prawn_version
|
45
|
+
if (Prawn::VERSION.split(".").map(&:to_i) <=> [2, 0, 4]) == -1
|
46
|
+
raise SkipElementError, "Prawn 2.0.4+ must be used if you'd like prawn-svg to render gradients"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def load_gradient_configuration
|
51
|
+
@units = attributes["gradientunits"] == 'userSpaceOnUse' ? :user_space : :bounding_box
|
52
|
+
|
53
|
+
if transform = attributes["gradienttransform"]
|
54
|
+
matrix = transform.split(COMMA_WSP_REGEXP).map(&:to_f)
|
55
|
+
if matrix != [1, 0, 0, 1, 0, 0]
|
56
|
+
raise SkipElementError, "prawn-svg does not yet support gradients with a non-identity gradientTransform attribute"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if (spread_method = attributes['spreadmethod']) && spread_method != "pad"
|
61
|
+
warnings << "prawn-svg only currently supports the 'pad' spreadMethod attribute value"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def load_coordinates
|
66
|
+
case @units
|
67
|
+
when :bounding_box
|
68
|
+
@x1 = parse_zero_to_one(attributes["x1"], 0)
|
69
|
+
@y1 = parse_zero_to_one(attributes["y1"], 0)
|
70
|
+
@x2 = parse_zero_to_one(attributes["x2"], 1)
|
71
|
+
@y2 = parse_zero_to_one(attributes["y2"], 0)
|
72
|
+
|
73
|
+
when :user_space
|
74
|
+
@x1 = x(attributes["x1"])
|
75
|
+
@y1 = y(attributes["y1"])
|
76
|
+
@x2 = x(attributes["x2"])
|
77
|
+
@y2 = y(attributes["y2"])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def load_stops
|
82
|
+
stop_elements = source.elements.map do |child|
|
83
|
+
element = Prawn::SVG::Elements::Base.new(document, child, [], {})
|
84
|
+
element.process
|
85
|
+
element
|
86
|
+
end.select do |element|
|
87
|
+
element.name == 'stop' && element.attributes["offset"]
|
88
|
+
end
|
89
|
+
|
90
|
+
@stops = stop_elements.each.with_object([]) do |child, result|
|
91
|
+
offset = parse_zero_to_one(child.attributes["offset"])
|
92
|
+
|
93
|
+
# Offsets must be strictly increasing (SVG 13.2.4)
|
94
|
+
if result.last && result.last.first > offset
|
95
|
+
offset = result.last.first
|
96
|
+
end
|
97
|
+
|
98
|
+
if color_hex = Prawn::SVG::Color.color_to_hex(child.attributes["stop-color"])
|
99
|
+
result << [offset, color_hex]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
raise SkipElementError, "gradient does not have any valid stops" if @stops.empty?
|
104
|
+
|
105
|
+
@stops.unshift([0, @stops.first.last]) if @stops.first.first > 0
|
106
|
+
@stops.push([1, @stops.last.last]) if @stops.last.first < 1
|
107
|
+
end
|
108
|
+
|
109
|
+
def parse_zero_to_one(string, default = 0)
|
110
|
+
string = string.to_s.strip
|
111
|
+
return default if string == ""
|
112
|
+
|
113
|
+
value = string.to_f
|
114
|
+
value /= 100.0 if string[-1..-1] == '%'
|
115
|
+
[0.0, value, 1.0].sort[1]
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
|
2
|
+
class FakeIO
|
3
|
+
def initialize(data)
|
4
|
+
@data = data
|
5
|
+
end
|
6
|
+
def read
|
7
|
+
@data
|
8
|
+
end
|
9
|
+
def rewind
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse
|
14
|
+
require_attributes 'width', 'height'
|
15
|
+
|
16
|
+
raise SkipElementQuietly if state[:display] == "none"
|
17
|
+
|
18
|
+
@url = attributes['xlink:href'] || attributes['href']
|
19
|
+
if @url.nil?
|
20
|
+
raise SkipElementError, "image tag must have an xlink:href"
|
21
|
+
end
|
22
|
+
|
23
|
+
if !@document.url_loader.valid?(@url)
|
24
|
+
raise SkipElementError, "image tag xlink:href attribute must use http, https or data scheme"
|
25
|
+
end
|
26
|
+
|
27
|
+
x = x(attributes['x'] || 0)
|
28
|
+
y = y(attributes['y'] || 0)
|
29
|
+
width = distance(attributes['width'])
|
30
|
+
height = distance(attributes['height'])
|
31
|
+
|
32
|
+
raise SkipElementQuietly if width.zero? || height.zero?
|
33
|
+
require_positive_value width, height
|
34
|
+
|
35
|
+
@image = begin
|
36
|
+
@document.url_loader.load(@url)
|
37
|
+
rescue => e
|
38
|
+
raise SkipElementError, "Error retrieving URL #{@url}: #{e.message}"
|
39
|
+
end
|
40
|
+
|
41
|
+
@aspect = Prawn::SVG::Calculators::AspectRatio.new(attributes['preserveAspectRatio'], [width, height], image_dimensions(@image))
|
42
|
+
|
43
|
+
@clip_x = x
|
44
|
+
@clip_y = y
|
45
|
+
@clip_width = width
|
46
|
+
@clip_height = height
|
47
|
+
|
48
|
+
@width = @aspect.width
|
49
|
+
@height = @aspect.height
|
50
|
+
@x = x + @aspect.x
|
51
|
+
@y = y - @aspect.y
|
52
|
+
end
|
53
|
+
|
54
|
+
def apply
|
55
|
+
if @aspect.slice?
|
56
|
+
add_call "save"
|
57
|
+
add_call "rectangle", [@clip_x, @clip_y], @clip_width, @clip_height
|
58
|
+
add_call "clip"
|
59
|
+
end
|
60
|
+
|
61
|
+
options = {:width => @width, :height => @height, :at => [@x, @y]}
|
62
|
+
|
63
|
+
add_call "image", FakeIO.new(@image), options
|
64
|
+
add_call "restore" if @aspect.slice?
|
65
|
+
end
|
66
|
+
|
67
|
+
def bounding_box
|
68
|
+
[@x, @y, @x + @width, @y - @height]
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
|
73
|
+
def image_dimensions(data)
|
74
|
+
handler = if data[0, 3].unpack("C*") == [255, 216, 255]
|
75
|
+
Prawn::Images::JPG
|
76
|
+
elsif data[0, 8].unpack("C*") == [137, 80, 78, 71, 13, 10, 26, 10]
|
77
|
+
Prawn::Images::PNG
|
78
|
+
else
|
79
|
+
raise SkipElementError, "Unsupported image type supplied to image tag; Prawn only supports JPG and PNG"
|
80
|
+
end
|
81
|
+
|
82
|
+
image = handler.new(data)
|
83
|
+
[image.width.to_f, image.height.to_f]
|
84
|
+
end
|
85
|
+
end
|