/
stretchable.rb
150 lines (128 loc) · 5.07 KB
/
stretchable.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#--
# $Id: stretchable.rb,v 1.2 2005/12/31 14:41:04 rmagick Exp $
# Copyright (C) 2006 Timothy P. Hunter
#++
class Magick::RVG
module PreserveAspectRatio
#--
# Included in Stretchable module and Image class
#++
# Specifies how the image within a viewport should be scaled.
# [+align+] a combination of 'xMin', 'xMid', or 'xMax', followed by
# 'YMin', 'YMid', or 'YMax'
# [+meet_or_slice+] one of 'meet' or 'slice'
def preserve_aspect_ratio(align, meet_or_slice='meet')
@align = align.to_s
if @align != 'none'
m = /\A(xMin|xMid|xMax)(YMin|YMid|YMax)\z/.match(@align)
raise(ArgumentError, "unknown alignment specifier: #{@align}") unless m
end
if meet_or_slice
meet_or_slice = meet_or_slice.to_s.downcase
if meet_or_slice == 'meet' || meet_or_slice == 'slice'
@meet_or_slice = meet_or_slice
else
raise(ArgumentError, "specifier must be `meet' or `slice' (got #{meet_or_slice})")
end
end
yield(self) if block_given?
self
end
end # module PreserveAspectRatio
# The methods in this module describe the user-coordinate space.
# Only RVG objects are stretchable.
module Stretchable
private
def set_viewbox_none(width, height)
sx, sy = 1.0, 1.0
tx, ty = @vbx_x, @vbx_y
if @vbx_width
sx = width / @vbx_width
end
if @vbx_height
sy = height / @vbx_height
end
return [tx, ty, sx, sy]
end
# Use align attribute to compute x- and y-offset from viewport's upper-left corner.
def align_to_viewport(width, height, sx, sy)
tx, ty = @vbx_x, @vbx_y
tx += case @align
when /\AxMin/
0
when NilClass, /\AxMid/
(width - @vbx_width*sx) / 2.0
when /\AxMax/
width - @vbx_width*sx
end
ty += case @align
when /YMin\z/
0
when NilClass, /YMid\z/
(height - @vbx_height*sy) / 2.0
when /YMax\z/
height - @vbx_height*sy
end
return [tx, ty]
end
# Scale to smaller viewbox dimension
def set_viewbox_meet(width, height)
sx = sy = [width / @vbx_width, height / @vbx_height].min
tx, ty = align_to_viewport(width, height, sx, sy)
return [tx, ty, sx, sy]
end
# Scale to larger viewbox dimension
def set_viewbox_slice(width, height)
sx = sy = [width / @vbx_width, height / @vbx_height].max
tx, ty = align_to_viewport(width, height, sx, sy)
return [tx, ty, sx, sy]
end
# Establish the viewbox as necessary
def add_viewbox_primitives(width, height, gc)
@vbx_width ||= width
@vbx_height ||= height
@vbx_x ||= 0.0
@vbx_y ||= 0.0
if @align == 'none'
tx, ty, sx, sy = set_viewbox_none(width, height)
elsif @meet_or_slice == 'meet'
tx, ty, sx, sy = set_viewbox_meet(width, height)
else
tx, ty, sx, sy = set_viewbox_slice(width, height)
end
# Establish clipping path around the current viewport
name = __id__.to_s
gc.define_clip_path(name) do
gc.path("M0,0 l#{width},0 l0,#{height} l-#{width},0 l0,-#{height}z")
end
gc.clip_path(name)
gc.translate(tx, ty) if (tx.abs > 1.0e-10 || ty.abs > 1.0e-10)
gc.scale(sx, sy) if (sx != 1.0 || sy != 1.0)
end
def initialize(*args, &block)
super()
@vbx_x, @vbx_y, @vbx_width, @vbx_height = nil
@meet_or_slice = 'meet'
@align = nil
end
public
include PreserveAspectRatio
# Describe a user coordinate system to be imposed on the viewbox.
# The arguments must be numbers and the +width+ and +height+
# arguments must be positive.
def viewbox(x, y, width, height)
begin
@vbx_x = Float(x)
@vbx_y = Float(y)
@vbx_width = Float(width)
@vbx_height = Float(height)
rescue ArgumentError
raise ArgumentError, "arguments must be convertable to float (got #{x.class}, #{y.class}, #{width.class}, #{height.class})"
end
raise(ArgumentError, "viewbox width must be > 0 (#{width} given)") unless width >= 0
raise(ArgumentError, "viewbox height must be > 0 (#{height} given)") unless height >= 0
yield(self) if block_given?
self
end
end # module Stretchable
end # class Magick::RVG