rmagick 1.7.4 → 1.8.0

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.

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