rmagick4j 0.3.5

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