rmagick 2.15.3 → 2.15.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rmagick might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.editorconfig +14 -0
- data/.rubocop.yml +27 -3
- data/.travis.yml +9 -6
- data/CHANGELOG.md +4 -0
- data/CONTRIBUTING.md +10 -0
- data/README.textile +4 -0
- data/before_install_linux.sh +4 -4
- data/doc/ex/gravity.rb +2 -1
- data/ext/RMagick/extconf.rb +60 -17
- data/lib/rmagick/version.rb +1 -1
- data/lib/rvg/clippath.rb +37 -36
- data/lib/rvg/container.rb +106 -107
- data/lib/rvg/deep_equal.rb +46 -48
- data/lib/rvg/describable.rb +35 -36
- data/lib/rvg/embellishable.rb +384 -380
- data/lib/rvg/misc.rb +705 -690
- data/lib/rvg/paint.rb +39 -40
- data/lib/rvg/pathdata.rb +120 -121
- data/lib/rvg/rvg.rb +212 -209
- data/lib/rvg/stretchable.rb +159 -158
- data/lib/rvg/stylable.rb +99 -100
- data/lib/rvg/text.rb +0 -1
- data/lib/rvg/transformable.rb +110 -110
- data/lib/rvg/units.rb +58 -58
- data/rmagick.gemspec +1 -1
- data/spec/rmagick/image/blue_shift_spec.rb +16 -0
- data/spec/rmagick/image/composite_spec.rb +140 -0
- data/spec/rmagick/image/constitute_spec.rb +15 -0
- data/spec/rmagick/image/dispatch_spec.rb +18 -0
- data/spec/rmagick/image/from_blob_spec.rb +14 -0
- data/spec/rmagick/image/ping_spec.rb +14 -0
- data/spec/rmagick/image/properties_spec.rb +29 -0
- data/spec/spec_helper.rb +3 -0
- data/test/Image1.rb +524 -718
- data/test/Image2.rb +1262 -1262
- data/test/Image3.rb +973 -973
- data/test/ImageList2.rb +341 -341
- data/test/Image_attributes.rb +678 -678
- data/test/Info.rb +336 -336
- data/test/Magick.rb +245 -242
- data/test/Pixel.rb +105 -105
- data/test/Preview.rb +42 -42
- metadata +21 -6
data/lib/rvg/misc.rb
CHANGED
@@ -1,708 +1,723 @@
|
|
1
1
|
# $Id: misc.rb,v 1.17 2010/03/21 01:43:01 baror Exp $
|
2
2
|
# Copyright (C) 2009 Timothy P. Hunter
|
3
3
|
module Magick
|
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
|
-
|
4
|
+
class RVG
|
5
|
+
# This is a standard deep_copy method that is used in most classes.
|
6
|
+
# Thanks to Robert Klemme.
|
7
|
+
module Duplicatable
|
8
|
+
def deep_copy(h = {})
|
9
|
+
# Prevent recursion. If we reach the
|
10
|
+
# object we started with, stop copying.
|
11
|
+
copy = h[__id__]
|
12
|
+
unless copy
|
13
|
+
h[__id__] = copy = self.class.allocate
|
14
|
+
ivars = instance_variables
|
15
|
+
ivars.each do |ivar|
|
16
|
+
ivalue = instance_variable_get(ivar)
|
17
|
+
cvalue = case
|
18
|
+
when NilClass === ivalue, Symbol === ivalue, Float === ivalue,
|
19
|
+
Fixnum === ivalue, FalseClass === ivalue, TrueClass === ivalue
|
20
|
+
ivalue
|
21
|
+
when ivalue.respond_to?(:deep_copy)
|
22
|
+
ivalue.deep_copy(h)
|
23
|
+
when ivalue.respond_to?(:dup)
|
24
|
+
ivalue.dup
|
25
|
+
else
|
26
|
+
ivalue
|
27
|
+
end
|
28
|
+
copy.instance_variable_set(ivar, cvalue)
|
29
|
+
end
|
30
|
+
copy.freeze if frozen?
|
31
|
+
end
|
32
|
+
copy
|
33
|
+
end
|
34
|
+
end # module Duplicatable
|
35
|
+
|
36
|
+
# Convert an array of method arguments to Float objects. If any
|
37
|
+
# cannot be converted, raise ArgumentError and issue a message.
|
38
|
+
def self.fmsg(*args)
|
39
|
+
"at least one argument cannot be converted to Float (got #{args.collect {|a| a.class}.join(', ')})"
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.convert_to_float(*args)
|
43
|
+
allow_nil = false
|
44
|
+
if args.last == :allow_nil
|
45
|
+
allow_nil = true
|
46
|
+
args.pop
|
47
|
+
end
|
48
|
+
begin
|
49
|
+
fargs = args.collect { |a| (allow_nil && a.nil?) ? a : Float(a) }
|
50
|
+
rescue ArgumentError, TypeError
|
51
|
+
raise ArgumentError, fmsg(*args)
|
52
|
+
end
|
53
|
+
fargs
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.convert_one_to_float(arg)
|
57
|
+
begin
|
58
|
+
farg = Float(arg)
|
59
|
+
rescue ArgumentError, TypeError
|
60
|
+
raise ArgumentError, "argument cannot be converted to Float (got #{arg.class})"
|
61
|
+
end
|
62
|
+
farg
|
63
|
+
end
|
64
|
+
end # class RVG
|
65
|
+
end # module Magick
|
66
|
+
|
67
|
+
module Magick
|
68
|
+
class RVG
|
69
|
+
class Utility
|
70
|
+
class TextStrategy
|
71
|
+
def initialize(context)
|
72
|
+
@ctx = context
|
73
|
+
@ctx.shadow.affine = @ctx.text_attrs.affine
|
74
|
+
end
|
75
|
+
|
76
|
+
def enquote(text)
|
77
|
+
if text.length > 2 && /\A(?:\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})\z/.match(text)
|
78
|
+
return text
|
79
|
+
elsif !text['\'']
|
80
|
+
text = '\''+text+'\''
|
81
|
+
return text
|
82
|
+
elsif !text['"']
|
83
|
+
text = '"'+text+'"'
|
84
|
+
return text
|
85
|
+
elsif !(text['{'] || text['}'])
|
86
|
+
text = '{'+text+'}'
|
87
|
+
return text
|
88
|
+
end
|
89
|
+
|
90
|
+
# escape existing braces, surround with braces
|
91
|
+
text.gsub!(/[}]/) { |b| '\\' + b }
|
92
|
+
'{' + text + '}'
|
93
|
+
end
|
94
|
+
|
95
|
+
def glyph_metrics(glyph_orientation, glyph)
|
96
|
+
gm = @ctx.shadow.get_type_metrics('a' + glyph + 'a')
|
97
|
+
gm2 = @ctx.shadow.get_type_metrics('aa')
|
98
|
+
h = (gm.ascent - gm.descent + 0.5).to_i
|
99
|
+
w = gm.width - gm2.width
|
100
|
+
if glyph_orientation == 0 || glyph_orientation == 180
|
101
|
+
[w, h]
|
102
|
+
else
|
103
|
+
[h, w]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def text_rel_coords(text)
|
108
|
+
y_rel_coords = []
|
109
|
+
x_rel_coords = []
|
110
|
+
first_word = true
|
111
|
+
words = text.split(::Magick::RVG::WORD_SEP)
|
112
|
+
words.each do |word|
|
113
|
+
unless first_word
|
114
|
+
wx, wy = get_word_spacing
|
115
|
+
x_rel_coords << wx
|
116
|
+
y_rel_coords << wy
|
117
|
+
end
|
118
|
+
first_word = false
|
119
|
+
word.split('').each do |glyph|
|
120
|
+
wx, wy = get_letter_spacing(glyph)
|
121
|
+
x_rel_coords << wx
|
122
|
+
y_rel_coords << wy
|
33
123
|
end
|
34
|
-
|
124
|
+
end
|
125
|
+
[x_rel_coords, y_rel_coords]
|
126
|
+
end
|
127
|
+
|
128
|
+
def shift_baseline(glyph_orientation, glyph)
|
129
|
+
glyph_dimensions = @ctx.shadow.get_type_metrics(glyph)
|
130
|
+
if glyph_orientation == 0 || glyph_orientation == 180
|
131
|
+
x = glyph_dimensions.width
|
132
|
+
else
|
133
|
+
x = glyph_dimensions.ascent - glyph_dimensions.descent
|
134
|
+
end
|
135
|
+
case @ctx.text_attrs.baseline_shift
|
136
|
+
when :baseline
|
137
|
+
x = 0
|
138
|
+
when :sub
|
139
|
+
|
140
|
+
when :super
|
141
|
+
x = -x
|
142
|
+
when /[-+]?(\d+)%/
|
143
|
+
m = $1 == '-' ? -1.0 : 1.0
|
144
|
+
x = (m * x * $1.to_f / 100.0)
|
145
|
+
else
|
146
|
+
x = -@ctx.text_attrs.baseline_shift
|
147
|
+
end
|
148
|
+
x
|
149
|
+
end
|
150
|
+
|
151
|
+
def render_glyph(glyph_orientation, x, y, glyph)
|
152
|
+
if glyph_orientation == 0
|
153
|
+
@ctx.gc.text(x, y, enquote(glyph))
|
154
|
+
else
|
155
|
+
@ctx.gc.push
|
156
|
+
@ctx.gc.translate(x, y)
|
157
|
+
@ctx.gc.rotate(glyph_orientation)
|
158
|
+
@ctx.gc.translate(-x, -y)
|
159
|
+
@ctx.gc.text(x, y, enquote(glyph))
|
160
|
+
@ctx.gc.pop
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end # class TextStrategy
|
164
|
+
|
165
|
+
class LRTextStrategy < TextStrategy
|
166
|
+
def get_word_spacing
|
167
|
+
@word_space ||= glyph_metrics(@ctx.text_attrs.glyph_orientation_horizontal, ' ')[0]
|
168
|
+
[@word_space + @ctx.text_attrs.word_spacing, 0]
|
169
|
+
end
|
35
170
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
"at least one argument cannot be converted to Float (got #{args.collect {|a| a.class}.join(', ')})"
|
171
|
+
def get_letter_spacing(glyph)
|
172
|
+
gx, gy = glyph_metrics(@ctx.text_attrs.glyph_orientation_horizontal, glyph)
|
173
|
+
[gx+@ctx.text_attrs.letter_spacing, gy]
|
40
174
|
end
|
41
175
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
176
|
+
def render(x, y, text)
|
177
|
+
x_rel_coords, y_rel_coords = text_rel_coords(text)
|
178
|
+
dx = x_rel_coords.inject(0) {|sum, a| sum + a}
|
179
|
+
dy = y_rel_coords.max
|
180
|
+
|
181
|
+
# We're handling the anchoring.
|
182
|
+
@ctx.gc.push
|
183
|
+
@ctx.gc.text_anchor(Magick::StartAnchor)
|
184
|
+
if @ctx.text_attrs.text_anchor == :end
|
185
|
+
x -= dx
|
186
|
+
elsif @ctx.text_attrs.text_anchor == :middle
|
187
|
+
x -= dx / 2
|
188
|
+
end
|
189
|
+
|
190
|
+
# Align the first glyph
|
191
|
+
case @ctx.text_attrs.glyph_orientation_horizontal
|
192
|
+
when 0
|
193
|
+
|
194
|
+
when 90
|
195
|
+
y -= dy
|
196
|
+
when 180
|
197
|
+
x += x_rel_coords.shift
|
198
|
+
x_rel_coords << 0
|
199
|
+
y -= dy
|
200
|
+
when 270
|
201
|
+
x += x_rel_coords[0]
|
202
|
+
end
|
203
|
+
|
204
|
+
y += shift_baseline(@ctx.text_attrs.glyph_orientation_horizontal, text[0,1])
|
205
|
+
|
206
|
+
first_word = true
|
207
|
+
text.split(::Magick::RVG::WORD_SEP).each do |word|
|
208
|
+
unless first_word
|
209
|
+
x += x_rel_coords.shift
|
47
210
|
end
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
211
|
+
first_word = false
|
212
|
+
word.split('').each do |glyph|
|
213
|
+
render_glyph(@ctx.text_attrs.glyph_orientation_horizontal, x, y, glyph)
|
214
|
+
x += x_rel_coords.shift
|
52
215
|
end
|
53
|
-
|
216
|
+
end
|
217
|
+
|
218
|
+
@ctx.gc.pop
|
219
|
+
[dx, 0]
|
54
220
|
end
|
221
|
+
end # class LRTextStrategy
|
55
222
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
223
|
+
class RLTextStrategy < TextStrategy
|
224
|
+
def render(x, y, text)
|
225
|
+
fail NotImplementedError
|
226
|
+
end
|
227
|
+
end # class RLTextStrategy
|
228
|
+
|
229
|
+
class TBTextStrategy < TextStrategy
|
230
|
+
def get_word_spacing
|
231
|
+
@word_space ||= glyph_metrics(@ctx.text_attrs.glyph_orientation_vertical, ' ')[1]
|
232
|
+
[0, @word_space + @ctx.text_attrs.word_spacing]
|
233
|
+
end
|
234
|
+
|
235
|
+
def get_letter_spacing(glyph)
|
236
|
+
gx, gy = glyph_metrics(@ctx.text_attrs.glyph_orientation_vertical, glyph)
|
237
|
+
[gx, gy+@ctx.text_attrs.letter_spacing]
|
238
|
+
end
|
239
|
+
|
240
|
+
def render(x, y, text)
|
241
|
+
x_rel_coords, y_rel_coords = text_rel_coords(text)
|
242
|
+
dx = x_rel_coords.max
|
243
|
+
dy = y_rel_coords.inject(0) {|sum, a| sum + a}
|
244
|
+
|
245
|
+
# We're handling the anchoring.
|
246
|
+
@ctx.gc.push
|
247
|
+
@ctx.gc.text_anchor(Magick::StartAnchor)
|
248
|
+
if @ctx.text_attrs.text_anchor == :end
|
249
|
+
y -= dy
|
250
|
+
elsif @ctx.text_attrs.text_anchor == :middle
|
251
|
+
y -= dy / 2
|
252
|
+
end
|
253
|
+
|
254
|
+
# Align the first glyph such that its center
|
255
|
+
# is aligned on x and its top is aligned on y.
|
256
|
+
|
257
|
+
case @ctx.text_attrs.glyph_orientation_vertical
|
258
|
+
when 0
|
259
|
+
x -= x_rel_coords.max / 2
|
260
|
+
y += y_rel_coords[0]
|
261
|
+
when 90
|
262
|
+
x -= x_rel_coords.max / 2
|
263
|
+
when 180
|
264
|
+
x += x_rel_coords.max / 2
|
265
|
+
when 270
|
266
|
+
x += x_rel_coords.max / 2
|
267
|
+
y += y_rel_coords.shift
|
268
|
+
y_rel_coords << 0 # since we used an element we need to add a dummy
|
269
|
+
end
|
270
|
+
|
271
|
+
x -= shift_baseline(@ctx.text_attrs.glyph_orientation_vertical, text[0,1])
|
272
|
+
|
273
|
+
first_word = true
|
274
|
+
text.split(::Magick::RVG::WORD_SEP).each do |word|
|
275
|
+
unless first_word
|
276
|
+
y += y_rel_coords.shift
|
277
|
+
x_rel_coords.shift
|
278
|
+
end
|
279
|
+
first_word = false
|
280
|
+
word.split('').each do |glyph|
|
281
|
+
case @ctx.text_attrs.glyph_orientation_vertical.to_i
|
282
|
+
when 0, 90, 270
|
283
|
+
x_shift = (dx - x_rel_coords.shift) / 2
|
284
|
+
when 180
|
285
|
+
x_shift = -(dx - x_rel_coords.shift) / 2
|
286
|
+
end
|
287
|
+
|
288
|
+
render_glyph(@ctx.text_attrs.glyph_orientation_vertical, x+x_shift, y, glyph)
|
289
|
+
y += y_rel_coords.shift
|
61
290
|
end
|
62
|
-
|
291
|
+
end
|
292
|
+
|
293
|
+
@ctx.gc.pop
|
294
|
+
[0, dy]
|
295
|
+
end
|
296
|
+
end # class TBTextStrategy
|
297
|
+
|
298
|
+
# Handle "easy" text
|
299
|
+
class DefaultTextStrategy < TextStrategy
|
300
|
+
def render(x, y, text)
|
301
|
+
@ctx.gc.text(x, y, enquote(text))
|
302
|
+
tm = @ctx.shadow.get_type_metrics(text)
|
303
|
+
dx = case @ctx.text_attrs.text_anchor
|
304
|
+
when :start
|
305
|
+
tm.width
|
306
|
+
when :middle
|
307
|
+
tm.width / 2
|
308
|
+
when :end
|
309
|
+
0
|
310
|
+
end
|
311
|
+
[dx, 0]
|
63
312
|
end
|
64
|
-
|
313
|
+
end # class NormalTextStrategy
|
314
|
+
end # class Utility
|
315
|
+
end # class RVG
|
65
316
|
end # module Magick
|
66
317
|
|
67
318
|
module Magick
|
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
|
-
end
|
94
|
-
|
95
|
-
def glyph_metrics(glyph_orientation, glyph)
|
96
|
-
gm = @ctx.shadow.get_type_metrics('a' + glyph + 'a')
|
97
|
-
gm2 = @ctx.shadow.get_type_metrics('aa')
|
98
|
-
h = (gm.ascent - gm.descent + 0.5).to_i
|
99
|
-
w = gm.width - gm2.width
|
100
|
-
if glyph_orientation == 0 || glyph_orientation == 180
|
101
|
-
[w, h]
|
102
|
-
else
|
103
|
-
[h, w]
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def text_rel_coords(text)
|
108
|
-
y_rel_coords = []
|
109
|
-
x_rel_coords = []
|
110
|
-
first_word = true
|
111
|
-
words = text.split(::Magick::RVG::WORD_SEP)
|
112
|
-
words.each do |word|
|
113
|
-
unless first_word
|
114
|
-
wx, wy = get_word_spacing
|
115
|
-
x_rel_coords << wx
|
116
|
-
y_rel_coords << wy
|
117
|
-
end
|
118
|
-
first_word = false
|
119
|
-
word.split('').each do |glyph|
|
120
|
-
wx, wy = get_letter_spacing(glyph)
|
121
|
-
x_rel_coords << wx
|
122
|
-
y_rel_coords << wy
|
123
|
-
end
|
124
|
-
end
|
125
|
-
[x_rel_coords, y_rel_coords]
|
126
|
-
end
|
127
|
-
|
128
|
-
def shift_baseline(glyph_orientation, glyph)
|
129
|
-
glyph_dimensions = @ctx.shadow.get_type_metrics(glyph)
|
130
|
-
if glyph_orientation == 0 || glyph_orientation == 180
|
131
|
-
x = glyph_dimensions.width
|
132
|
-
else
|
133
|
-
x = glyph_dimensions.ascent - glyph_dimensions.descent
|
134
|
-
end
|
135
|
-
case @ctx.text_attrs.baseline_shift
|
136
|
-
when :baseline
|
137
|
-
x = 0
|
138
|
-
when :sub
|
139
|
-
|
140
|
-
when :super
|
141
|
-
x = -x
|
142
|
-
when /[-+]?(\d+)%/
|
143
|
-
m = $1 == '-' ? -1.0 : 1.0
|
144
|
-
x = (m * x * $1.to_f / 100.0)
|
145
|
-
else
|
146
|
-
x = -@ctx.text_attrs.baseline_shift
|
147
|
-
end
|
148
|
-
x
|
149
|
-
end
|
150
|
-
|
151
|
-
def render_glyph(glyph_orientation, x, y, glyph)
|
152
|
-
if glyph_orientation == 0
|
153
|
-
@ctx.gc.text(x, y, enquote(glyph))
|
154
|
-
else
|
155
|
-
@ctx.gc.push
|
156
|
-
@ctx.gc.translate(x, y)
|
157
|
-
@ctx.gc.rotate(glyph_orientation)
|
158
|
-
@ctx.gc.translate(-x, -y)
|
159
|
-
@ctx.gc.text(x, y, enquote(glyph))
|
160
|
-
@ctx.gc.pop
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end # class TextStrategy
|
164
|
-
|
165
|
-
class LRTextStrategy < TextStrategy
|
166
|
-
def get_word_spacing
|
167
|
-
@word_space ||= glyph_metrics(@ctx.text_attrs.glyph_orientation_horizontal, ' ')[0]
|
168
|
-
[@word_space + @ctx.text_attrs.word_spacing, 0]
|
169
|
-
end
|
170
|
-
|
171
|
-
def get_letter_spacing(glyph)
|
172
|
-
gx, gy = glyph_metrics(@ctx.text_attrs.glyph_orientation_horizontal, glyph)
|
173
|
-
[gx+@ctx.text_attrs.letter_spacing, gy]
|
174
|
-
end
|
175
|
-
|
176
|
-
def render(x, y, text)
|
177
|
-
x_rel_coords, y_rel_coords = text_rel_coords(text)
|
178
|
-
dx = x_rel_coords.inject(0) {|sum, a| sum + a}
|
179
|
-
dy = y_rel_coords.max
|
180
|
-
|
181
|
-
# We're handling the anchoring.
|
182
|
-
@ctx.gc.push
|
183
|
-
@ctx.gc.text_anchor(Magick::StartAnchor)
|
184
|
-
if @ctx.text_attrs.text_anchor == :end
|
185
|
-
x -= dx
|
186
|
-
elsif @ctx.text_attrs.text_anchor == :middle
|
187
|
-
x -= dx / 2
|
188
|
-
end
|
189
|
-
|
190
|
-
# Align the first glyph
|
191
|
-
case @ctx.text_attrs.glyph_orientation_horizontal
|
192
|
-
when 0
|
193
|
-
|
194
|
-
when 90
|
195
|
-
y -= dy
|
196
|
-
when 180
|
197
|
-
x += x_rel_coords.shift
|
198
|
-
x_rel_coords << 0
|
199
|
-
y -= dy
|
200
|
-
when 270
|
201
|
-
x += x_rel_coords[0]
|
202
|
-
end
|
203
|
-
|
204
|
-
y += shift_baseline(@ctx.text_attrs.glyph_orientation_horizontal, text[0,1])
|
205
|
-
|
206
|
-
first_word = true
|
207
|
-
text.split(::Magick::RVG::WORD_SEP).each do |word|
|
208
|
-
unless first_word
|
209
|
-
x += x_rel_coords.shift
|
210
|
-
end
|
211
|
-
first_word = false
|
212
|
-
word.split('').each do |glyph|
|
213
|
-
render_glyph(@ctx.text_attrs.glyph_orientation_horizontal, x, y, glyph)
|
214
|
-
x += x_rel_coords.shift
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
@ctx.gc.pop
|
219
|
-
[dx, 0]
|
220
|
-
end
|
221
|
-
end # class LRTextStrategy
|
222
|
-
|
223
|
-
class RLTextStrategy < TextStrategy
|
224
|
-
def render(x, y, text)
|
225
|
-
fail NotImplementedError
|
226
|
-
end
|
227
|
-
end # class RLTextStrategy
|
228
|
-
|
229
|
-
class TBTextStrategy < TextStrategy
|
230
|
-
def get_word_spacing
|
231
|
-
@word_space ||= glyph_metrics(@ctx.text_attrs.glyph_orientation_vertical, ' ')[1]
|
232
|
-
[0, @word_space + @ctx.text_attrs.word_spacing]
|
233
|
-
end
|
234
|
-
|
235
|
-
def get_letter_spacing(glyph)
|
236
|
-
gx, gy = glyph_metrics(@ctx.text_attrs.glyph_orientation_vertical, glyph)
|
237
|
-
[gx, gy+@ctx.text_attrs.letter_spacing]
|
238
|
-
end
|
239
|
-
|
240
|
-
def render(x, y, text)
|
241
|
-
x_rel_coords, y_rel_coords = text_rel_coords(text)
|
242
|
-
dx = x_rel_coords.max
|
243
|
-
dy = y_rel_coords.inject(0) {|sum, a| sum + a}
|
244
|
-
|
245
|
-
# We're handling the anchoring.
|
246
|
-
@ctx.gc.push
|
247
|
-
@ctx.gc.text_anchor(Magick::StartAnchor)
|
248
|
-
if @ctx.text_attrs.text_anchor == :end
|
249
|
-
y -= dy
|
250
|
-
elsif @ctx.text_attrs.text_anchor == :middle
|
251
|
-
y -= dy / 2
|
252
|
-
end
|
253
|
-
|
254
|
-
# Align the first glyph such that its center
|
255
|
-
# is aligned on x and its top is aligned on y.
|
256
|
-
|
257
|
-
case @ctx.text_attrs.glyph_orientation_vertical
|
258
|
-
when 0
|
259
|
-
x -= x_rel_coords.max / 2
|
260
|
-
y += y_rel_coords[0]
|
261
|
-
when 90
|
262
|
-
x -= x_rel_coords.max / 2
|
263
|
-
when 180
|
264
|
-
x += x_rel_coords.max / 2
|
265
|
-
when 270
|
266
|
-
x += x_rel_coords.max / 2
|
267
|
-
y += y_rel_coords.shift
|
268
|
-
y_rel_coords << 0 # since we used an element we need to add a dummy
|
269
|
-
end
|
270
|
-
|
271
|
-
x -= shift_baseline(@ctx.text_attrs.glyph_orientation_vertical, text[0,1])
|
272
|
-
|
273
|
-
first_word = true
|
274
|
-
text.split(::Magick::RVG::WORD_SEP).each do |word|
|
275
|
-
unless first_word
|
276
|
-
y += y_rel_coords.shift
|
277
|
-
x_rel_coords.shift
|
278
|
-
end
|
279
|
-
first_word = false
|
280
|
-
word.split('').each do |glyph|
|
281
|
-
case @ctx.text_attrs.glyph_orientation_vertical.to_i
|
282
|
-
when 0, 90, 270
|
283
|
-
x_shift = (dx - x_rel_coords.shift) / 2
|
284
|
-
when 180
|
285
|
-
x_shift = -(dx - x_rel_coords.shift) / 2
|
286
|
-
end
|
287
|
-
|
288
|
-
render_glyph(@ctx.text_attrs.glyph_orientation_vertical, x+x_shift, y, glyph)
|
289
|
-
y += y_rel_coords.shift
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
@ctx.gc.pop
|
294
|
-
[0, dy]
|
295
|
-
end
|
296
|
-
end # class TBTextStrategy
|
297
|
-
|
298
|
-
# Handle "easy" text
|
299
|
-
class DefaultTextStrategy < TextStrategy
|
300
|
-
def render(x, y, text)
|
301
|
-
@ctx.gc.text(x, y, enquote(text))
|
302
|
-
tm = @ctx.shadow.get_type_metrics(text)
|
303
|
-
dx = case @ctx.text_attrs.text_anchor
|
304
|
-
when :start
|
305
|
-
tm.width
|
306
|
-
when :middle
|
307
|
-
tm.width / 2
|
308
|
-
when :end
|
309
|
-
0
|
310
|
-
end
|
311
|
-
[dx, 0]
|
312
|
-
end
|
313
|
-
end # class NormalTextStrategy
|
314
|
-
end # class Utility
|
315
|
-
end # class RVG
|
316
|
-
end # module Magick
|
319
|
+
class RVG
|
320
|
+
class Utility
|
321
|
+
class TextAttributes
|
322
|
+
public
|
323
|
+
|
324
|
+
WRITING_MODE = %w{lr-tb lr rl-tb rl tb-rl tb}
|
325
|
+
|
326
|
+
def initialize
|
327
|
+
@affine = []
|
328
|
+
@affine << Magick::AffineMatrix.new(1, 0, 0, 1, 0, 0)
|
329
|
+
@baseline_shift = []
|
330
|
+
@baseline_shift << :baseline
|
331
|
+
@glyph_orientation_horizontal = []
|
332
|
+
@glyph_orientation_horizontal << 0
|
333
|
+
@glyph_orientation_vertical = []
|
334
|
+
@glyph_orientation_vertical << 90
|
335
|
+
@letter_spacing = []
|
336
|
+
@letter_spacing << 0
|
337
|
+
@text_anchor = []
|
338
|
+
@text_anchor << :start
|
339
|
+
@word_spacing = []
|
340
|
+
@word_spacing << 0
|
341
|
+
@writing_mode = []
|
342
|
+
@writing_mode << 'lr-tb'
|
343
|
+
end
|
317
344
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
def word_spacing(value)
|
697
|
-
@text_attrs.word_spacing = Magick::RVG.convert_one_to_float(value)
|
698
|
-
nil
|
699
|
-
end
|
700
|
-
|
701
|
-
def writing_mode(mode)
|
702
|
-
@text_attrs.writing_mode = mode
|
703
|
-
nil
|
704
|
-
end
|
705
|
-
end # class GraphicContext
|
706
|
-
end # class Utility
|
707
|
-
end # class RVG
|
345
|
+
def push
|
346
|
+
@affine.push(@affine.last.dup)
|
347
|
+
@baseline_shift.push(@baseline_shift.last)
|
348
|
+
@text_anchor.push(@text_anchor.last)
|
349
|
+
@writing_mode.push(@writing_mode.last.dup)
|
350
|
+
@glyph_orientation_vertical.push(@glyph_orientation_vertical.last)
|
351
|
+
@glyph_orientation_horizontal.push(@glyph_orientation_horizontal.last)
|
352
|
+
@letter_spacing.push(@letter_spacing.last)
|
353
|
+
@word_spacing.push(@word_spacing.last)
|
354
|
+
end
|
355
|
+
|
356
|
+
def pop
|
357
|
+
@affine.pop
|
358
|
+
@baseline_shift.pop
|
359
|
+
@text_anchor.pop
|
360
|
+
@writing_mode.pop
|
361
|
+
@glyph_orientation_vertical.pop
|
362
|
+
@glyph_orientation_horizontal.pop
|
363
|
+
@letter_spacing.pop
|
364
|
+
@word_spacing.pop
|
365
|
+
end
|
366
|
+
|
367
|
+
def set_affine(sx, rx, ry, sy, tx, ty)
|
368
|
+
@affine[-1].sx = sx
|
369
|
+
@affine[-1].rx = rx
|
370
|
+
@affine[-1].ry = ry
|
371
|
+
@affine[-1].sy = sy
|
372
|
+
@affine[-1].tx = tx
|
373
|
+
@affine[-1].ty = ty
|
374
|
+
end
|
375
|
+
|
376
|
+
def affine
|
377
|
+
@affine[-1]
|
378
|
+
end
|
379
|
+
|
380
|
+
def baseline_shift
|
381
|
+
@baseline_shift[-1]
|
382
|
+
end
|
383
|
+
|
384
|
+
def baseline_shift=(value)
|
385
|
+
@baseline_shift[-1] = value
|
386
|
+
end
|
387
|
+
|
388
|
+
def text_anchor
|
389
|
+
@text_anchor[-1]
|
390
|
+
end
|
391
|
+
|
392
|
+
def text_anchor=(anchor)
|
393
|
+
@text_anchor[-1] = anchor
|
394
|
+
end
|
395
|
+
|
396
|
+
def glyph_orientation_vertical
|
397
|
+
@glyph_orientation_vertical[-1]
|
398
|
+
end
|
399
|
+
|
400
|
+
def glyph_orientation_vertical=(angle)
|
401
|
+
@glyph_orientation_vertical[-1] = angle
|
402
|
+
end
|
403
|
+
|
404
|
+
def glyph_orientation_horizontal
|
405
|
+
@glyph_orientation_horizontal[-1]
|
406
|
+
end
|
407
|
+
|
408
|
+
def glyph_orientation_horizontal=(angle)
|
409
|
+
@glyph_orientation_horizontal[-1] = angle
|
410
|
+
end
|
411
|
+
|
412
|
+
def letter_spacing
|
413
|
+
@letter_spacing[-1]
|
414
|
+
end
|
415
|
+
|
416
|
+
def letter_spacing=(value)
|
417
|
+
@letter_spacing[-1] = value
|
418
|
+
end
|
419
|
+
|
420
|
+
def non_default?
|
421
|
+
@baseline_shift[-1] != :baseline || @letter_spacing[-1] != 0 ||
|
422
|
+
@word_spacing[-1] != 0 || @writing_mode[-1][/\Alr/].nil? ||
|
423
|
+
@glyph_orientation_horizontal[-1] != 0
|
424
|
+
end
|
425
|
+
|
426
|
+
def word_spacing
|
427
|
+
@word_spacing[-1]
|
428
|
+
end
|
429
|
+
|
430
|
+
def word_spacing=(value)
|
431
|
+
@word_spacing[-1] = value
|
432
|
+
end
|
433
|
+
|
434
|
+
def writing_mode
|
435
|
+
@writing_mode[-1]
|
436
|
+
end
|
437
|
+
|
438
|
+
def writing_mode=(mode)
|
439
|
+
@writing_mode[-1] = WRITING_MODE.include?(mode) ? mode : 'lr-tb'
|
440
|
+
end
|
441
|
+
end # class TextAttributes
|
442
|
+
|
443
|
+
class GraphicContext
|
444
|
+
FONT_STRETCH = {
|
445
|
+
:normal => Magick::NormalStretch,
|
446
|
+
:ultra_condensed => Magick::UltraCondensedStretch,
|
447
|
+
:extra_condensed => Magick::ExtraCondensedStretch,
|
448
|
+
:condensed => Magick::CondensedStretch,
|
449
|
+
:semi_condensed => Magick::SemiCondensedStretch,
|
450
|
+
:semi_expanded => Magick::SemiExpandedStretch,
|
451
|
+
:expanded => Magick::ExpandedStretch,
|
452
|
+
:extra_expanded => Magick::ExtraExpandedStretch,
|
453
|
+
:ultra_expanded => Magick::UltraExpandedStretch
|
454
|
+
}
|
455
|
+
|
456
|
+
FONT_STYLE = {
|
457
|
+
:normal => Magick::NormalStyle,
|
458
|
+
:italic => Magick::ItalicStyle,
|
459
|
+
:oblique => Magick::ObliqueStyle
|
460
|
+
}
|
461
|
+
|
462
|
+
FONT_WEIGHT = {
|
463
|
+
'normal' => Magick::NormalWeight,
|
464
|
+
'bold' => Magick::BoldWeight,
|
465
|
+
'bolder' => Magick::BolderWeight,
|
466
|
+
'lighter' => Magick::LighterWeight
|
467
|
+
}
|
468
|
+
|
469
|
+
TEXT_ANCHOR = {
|
470
|
+
:start => Magick::StartAnchor,
|
471
|
+
:middle => Magick::MiddleAnchor,
|
472
|
+
:end => Magick::EndAnchor
|
473
|
+
}
|
474
|
+
|
475
|
+
ANCHOR_TO_ALIGN = {
|
476
|
+
:start => Magick::LeftAlign,
|
477
|
+
:middle => Magick::CenterAlign,
|
478
|
+
:end => Magick::RightAlign
|
479
|
+
}
|
480
|
+
|
481
|
+
TEXT_DECORATION = {
|
482
|
+
:none => Magick::NoDecoration,
|
483
|
+
:underline => Magick::UnderlineDecoration,
|
484
|
+
:overline => Magick::OverlineDecoration,
|
485
|
+
:line_through => Magick::LineThroughDecoration}
|
486
|
+
|
487
|
+
TEXT_STRATEGIES = {
|
488
|
+
'lr-tb' => LRTextStrategy, 'lr' => LRTextStrategy,
|
489
|
+
'rt-tb' => RLTextStrategy, 'rl' => RLTextStrategy,
|
490
|
+
'tb-rl' => TBTextStrategy, 'tb' => TBTextStrategy
|
491
|
+
}
|
492
|
+
|
493
|
+
def self.degrees_to_radians(deg)
|
494
|
+
Math::PI * (deg % 360.0) / 180.0
|
495
|
+
end
|
496
|
+
|
497
|
+
private
|
498
|
+
|
499
|
+
def init_matrix
|
500
|
+
@rx = @ry = 0
|
501
|
+
@sx = @sy = 1
|
502
|
+
@tx = @ty = 0
|
503
|
+
end
|
504
|
+
|
505
|
+
def concat_matrix
|
506
|
+
curr = @text_attrs.affine
|
507
|
+
sx = curr.sx * @sx + curr.ry * @rx
|
508
|
+
rx = curr.rx * @sx + curr.sy * @rx
|
509
|
+
ry = curr.sx * @ry + curr.ry * @sy
|
510
|
+
sy = curr.rx * @ry + curr.sy * @sy
|
511
|
+
tx = curr.sx * @tx + curr.ry * @ty + curr.tx
|
512
|
+
ty = curr.rx * @tx + curr.sy * @ty + curr.ty
|
513
|
+
@text_attrs.set_affine(sx, rx, ry, sy, tx, ty)
|
514
|
+
init_matrix
|
515
|
+
end
|
516
|
+
|
517
|
+
public
|
518
|
+
|
519
|
+
attr_reader :gc, :text_attrs
|
520
|
+
|
521
|
+
def initialize
|
522
|
+
@gc = Magick::Draw.new
|
523
|
+
@shadow = []
|
524
|
+
@shadow << Magick::Draw.new
|
525
|
+
@text_attrs = TextAttributes.new
|
526
|
+
init_matrix
|
527
|
+
end
|
528
|
+
|
529
|
+
def method_missing(methID, *args, &block)
|
530
|
+
@gc.__send__(methID, *args, &block)
|
531
|
+
end
|
532
|
+
|
533
|
+
def affine(sx, rx, ry, sy, tx, ty)
|
534
|
+
sx, rx, ry, sy, tx, ty = Magick::RVG.convert_to_float(sx, rx, ry, sy, tx, ty)
|
535
|
+
@gc.affine(sx, rx, ry, sy, tx, ty)
|
536
|
+
@text_attrs.set_affine(sx, rx, ry, sy, tx, ty)
|
537
|
+
nil
|
538
|
+
end
|
539
|
+
|
540
|
+
def baseline_shift(value)
|
541
|
+
@text_attrs.baseline_shift = case value
|
542
|
+
when 'baseline', 'sub', 'super'
|
543
|
+
value.intern
|
544
|
+
when /[-+]?\d+%/, Numeric
|
545
|
+
value
|
546
|
+
else
|
547
|
+
:baseline
|
548
|
+
end
|
549
|
+
nil
|
550
|
+
end
|
551
|
+
|
552
|
+
def font(name)
|
553
|
+
@gc.font(name)
|
554
|
+
@shadow[-1].font = name
|
555
|
+
nil
|
556
|
+
end
|
557
|
+
|
558
|
+
def font_family(name)
|
559
|
+
@gc.font_family(name)
|
560
|
+
@shadow[-1].font_family = name
|
561
|
+
nil
|
562
|
+
end
|
563
|
+
|
564
|
+
def font_size(points)
|
565
|
+
@gc.font_size(points)
|
566
|
+
@shadow[-1].pointsize = points
|
567
|
+
nil
|
568
|
+
end
|
569
|
+
|
570
|
+
def font_stretch(stretch)
|
571
|
+
stretch = FONT_STRETCH.fetch(stretch.intern, Magick::NormalStretch)
|
572
|
+
@gc.font_stretch(stretch)
|
573
|
+
@shadow[-1].font_stretch = stretch
|
574
|
+
nil
|
575
|
+
end
|
576
|
+
|
577
|
+
def font_style(style)
|
578
|
+
style = FONT_STYLE.fetch(style.intern, Magick::NormalStyle)
|
579
|
+
@gc.font_style(style)
|
580
|
+
@shadow[-1].font_style = style
|
581
|
+
nil
|
582
|
+
end
|
583
|
+
|
584
|
+
def font_weight(weight)
|
585
|
+
# If the arg is not in the hash use it directly. Handles numeric values.
|
586
|
+
weight = FONT_WEIGHT.fetch(weight) {|key| key}
|
587
|
+
@gc.font_weight(weight)
|
588
|
+
@shadow[-1].font_weight = weight
|
589
|
+
nil
|
590
|
+
end
|
591
|
+
|
592
|
+
def glyph_orientation_horizontal(deg)
|
593
|
+
deg = Magick::RVG.convert_one_to_float(deg)
|
594
|
+
@text_attrs.glyph_orientation_horizontal = (deg % 360) / 90 * 90
|
595
|
+
nil
|
596
|
+
end
|
597
|
+
|
598
|
+
def glyph_orientation_vertical(deg)
|
599
|
+
deg = Magick::RVG.convert_one_to_float(deg)
|
600
|
+
@text_attrs.glyph_orientation_vertical = (deg % 360) / 90 * 90
|
601
|
+
nil
|
602
|
+
end
|
603
|
+
|
604
|
+
def inspect
|
605
|
+
@gc.inspect
|
606
|
+
end
|
607
|
+
|
608
|
+
def letter_spacing(value)
|
609
|
+
@text_attrs.letter_spacing = Magick::RVG.convert_one_to_float(value)
|
610
|
+
nil
|
611
|
+
end
|
612
|
+
|
613
|
+
def push
|
614
|
+
@gc.push
|
615
|
+
@shadow.push(@shadow.last.dup)
|
616
|
+
@text_attrs.push
|
617
|
+
nil
|
618
|
+
end
|
619
|
+
|
620
|
+
def pop
|
621
|
+
@gc.pop
|
622
|
+
@shadow.pop
|
623
|
+
@text_attrs.pop
|
624
|
+
nil
|
625
|
+
end
|
626
|
+
|
627
|
+
def rotate(degrees)
|
628
|
+
degrees = Magick::RVG.convert_one_to_float(degrees)
|
629
|
+
@gc.rotate(degrees)
|
630
|
+
@sx = Math.cos(GraphicContext.degrees_to_radians(degrees))
|
631
|
+
@rx = Math.sin(GraphicContext.degrees_to_radians(degrees))
|
632
|
+
@ry = -Math.sin(GraphicContext.degrees_to_radians(degrees))
|
633
|
+
@sy = Math.cos(GraphicContext.degrees_to_radians(degrees))
|
634
|
+
concat_matrix
|
635
|
+
nil
|
636
|
+
end
|
637
|
+
|
638
|
+
def scale(sx, sy)
|
639
|
+
sx, sy = Magick::RVG.convert_to_float(sx, sy)
|
640
|
+
@gc.scale(sx, sy)
|
641
|
+
@sx = sx
|
642
|
+
@sy = sy
|
643
|
+
concat_matrix
|
644
|
+
nil
|
645
|
+
end
|
646
|
+
|
647
|
+
def shadow
|
648
|
+
@shadow.last
|
649
|
+
end
|
650
|
+
|
651
|
+
def skewX(degrees)
|
652
|
+
degrees = Magick::RVG.convert_one_to_float(degrees)
|
653
|
+
@gc.skewX(degrees)
|
654
|
+
@ry = Math.tan(GraphicContext.degrees_to_radians(degrees))
|
655
|
+
concat_matrix
|
656
|
+
nil
|
657
|
+
end
|
658
|
+
|
659
|
+
def skewY(degrees)
|
660
|
+
degrees = Magick::RVG.convert_one_to_float(degrees)
|
661
|
+
@gc.skewY(degrees)
|
662
|
+
@rx = Math.tan(GraphicContext.degrees_to_radians(degrees))
|
663
|
+
concat_matrix
|
664
|
+
nil
|
665
|
+
end
|
666
|
+
|
667
|
+
def stroke_width(width)
|
668
|
+
width = Magick::RVG.convert_one_to_float(width)
|
669
|
+
@gc.stroke_width(width)
|
670
|
+
@shadow[-1].stroke_width = width
|
671
|
+
nil
|
672
|
+
end
|
673
|
+
|
674
|
+
def text(x, y, text)
|
675
|
+
return if text.length == 0
|
676
|
+
if @text_attrs.non_default?
|
677
|
+
text_renderer = TEXT_STRATEGIES[@text_attrs.writing_mode].new(self)
|
678
|
+
else
|
679
|
+
text_renderer = DefaultTextStrategy.new(self)
|
680
|
+
end
|
681
|
+
|
682
|
+
text_renderer.render(x, y, text)
|
683
|
+
end
|
684
|
+
|
685
|
+
def text_anchor(anchor)
|
686
|
+
anchor = anchor.intern
|
687
|
+
anchor_enum = TEXT_ANCHOR.fetch(anchor, Magick::StartAnchor)
|
688
|
+
@gc.text_anchor(anchor_enum)
|
689
|
+
align = ANCHOR_TO_ALIGN.fetch(anchor, Magick::LeftAlign)
|
690
|
+
@shadow[-1].align = align
|
691
|
+
@text_attrs.text_anchor = anchor
|
692
|
+
nil
|
693
|
+
end
|
694
|
+
|
695
|
+
def text_decoration(decoration)
|
696
|
+
decoration = TEXT_DECORATION.fetch(decoration.intern, Magick::NoDecoration)
|
697
|
+
@gc.decorate(decoration)
|
698
|
+
@shadow[-1].decorate = decoration
|
699
|
+
nil
|
700
|
+
end
|
701
|
+
|
702
|
+
def translate(tx, ty)
|
703
|
+
tx, ty = Magick::RVG.convert_to_float(tx, ty)
|
704
|
+
@gc.translate(tx, ty)
|
705
|
+
@tx = tx
|
706
|
+
@ty = ty
|
707
|
+
concat_matrix
|
708
|
+
nil
|
709
|
+
end
|
710
|
+
|
711
|
+
def word_spacing(value)
|
712
|
+
@text_attrs.word_spacing = Magick::RVG.convert_one_to_float(value)
|
713
|
+
nil
|
714
|
+
end
|
715
|
+
|
716
|
+
def writing_mode(mode)
|
717
|
+
@text_attrs.writing_mode = mode
|
718
|
+
nil
|
719
|
+
end
|
720
|
+
end # class GraphicContext
|
721
|
+
end # class Utility
|
722
|
+
end # class RVG
|
708
723
|
end # module Magick
|