rmagick 2.13.4 → 2.16.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 (281) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +14 -0
  3. data/.hound.yml +2 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +340 -0
  6. data/.simplecov +27 -0
  7. data/.travis.yml +29 -12
  8. data/CHANGELOG.md +915 -0
  9. data/CODE_OF_CONDUCT.md +13 -0
  10. data/CONTRIBUTING.md +31 -3
  11. data/Gemfile +3 -0
  12. data/README.textile +94 -4
  13. data/Rakefile +57 -45
  14. data/before_install_linux.sh +26 -6
  15. data/deprecated/RMagick.rb +6 -0
  16. data/doc/comtasks.html +1 -1
  17. data/doc/draw.html +7 -4
  18. data/doc/ex/InitialCoords.rb +16 -17
  19. data/doc/ex/NewCoordSys.rb +21 -23
  20. data/doc/ex/OrigCoordSys.rb +8 -10
  21. data/doc/ex/PreserveAspectRatio.rb +182 -183
  22. data/doc/ex/RotateScale.rb +27 -28
  23. data/doc/ex/Skew.rb +27 -27
  24. data/doc/ex/Use01.rb +7 -8
  25. data/doc/ex/Use02.rb +11 -12
  26. data/doc/ex/Use03.rb +7 -7
  27. data/doc/ex/ViewBox.rb +16 -18
  28. data/doc/ex/adaptive_threshold.rb +4 -5
  29. data/doc/ex/add_noise.rb +4 -5
  30. data/doc/ex/affine.rb +4 -4
  31. data/doc/ex/affine_transform.rb +4 -4
  32. data/doc/ex/arc.rb +10 -10
  33. data/doc/ex/arcpath.rb +7 -8
  34. data/doc/ex/arcs01.rb +10 -10
  35. data/doc/ex/arcs02.rb +36 -38
  36. data/doc/ex/average.rb +5 -5
  37. data/doc/ex/axes.rb +4 -4
  38. data/doc/ex/baseline_shift01.rb +11 -12
  39. data/doc/ex/bilevel_channel.rb +2 -3
  40. data/doc/ex/blur_image.rb +2 -2
  41. data/doc/ex/border.rb +2 -2
  42. data/doc/ex/bounding_box.rb +9 -11
  43. data/doc/ex/cbezier1.rb +3 -4
  44. data/doc/ex/cbezier2.rb +3 -4
  45. data/doc/ex/cbezier3.rb +3 -4
  46. data/doc/ex/cbezier4.rb +3 -4
  47. data/doc/ex/cbezier5.rb +2 -3
  48. data/doc/ex/cbezier6.rb +3 -3
  49. data/doc/ex/channel.rb +10 -11
  50. data/doc/ex/charcoal.rb +2 -2
  51. data/doc/ex/chop.rb +2 -2
  52. data/doc/ex/circle.rb +3 -3
  53. data/doc/ex/circle01.rb +6 -7
  54. data/doc/ex/clip_path.rb +12 -12
  55. data/doc/ex/coalesce.rb +32 -35
  56. data/doc/ex/color_fill_to_border.rb +6 -6
  57. data/doc/ex/color_floodfill.rb +2 -2
  58. data/doc/ex/color_histogram.rb +17 -18
  59. data/doc/ex/color_reset.rb +2 -2
  60. data/doc/ex/colorize.rb +2 -2
  61. data/doc/ex/colors.rb +30 -30
  62. data/doc/ex/compose_mask.rb +8 -9
  63. data/doc/ex/composite.rb +14 -16
  64. data/doc/ex/composite_layers.rb +15 -15
  65. data/doc/ex/composite_tiled.rb +5 -7
  66. data/doc/ex/contrast.rb +12 -12
  67. data/doc/ex/crop.rb +3 -3
  68. data/doc/ex/crop_with_gravity.rb +13 -17
  69. data/doc/ex/cubic01.rb +18 -20
  70. data/doc/ex/cubic02.rb +64 -67
  71. data/doc/ex/cycle_colormap.rb +2 -2
  72. data/doc/ex/dissolve.rb +2 -3
  73. data/doc/ex/drawcomp.rb +34 -34
  74. data/doc/ex/drop_shadow.rb +8 -8
  75. data/doc/ex/edge.rb +2 -2
  76. data/doc/ex/ellipse.rb +4 -4
  77. data/doc/ex/ellipse01.rb +10 -11
  78. data/doc/ex/emboss.rb +2 -2
  79. data/doc/ex/enhance.rb +2 -2
  80. data/doc/ex/equalize.rb +2 -2
  81. data/doc/ex/evenodd.rb +31 -32
  82. data/doc/ex/fill_pattern.rb +12 -15
  83. data/doc/ex/flatten_images.rb +13 -13
  84. data/doc/ex/flip.rb +2 -2
  85. data/doc/ex/flop.rb +2 -2
  86. data/doc/ex/font_styles.rb +21 -23
  87. data/doc/ex/fonts.rb +14 -14
  88. data/doc/ex/frame.rb +3 -3
  89. data/doc/ex/gaussian_blur.rb +2 -2
  90. data/doc/ex/get_multiline_type_metrics.rb +6 -7
  91. data/doc/ex/get_pixels.rb +13 -14
  92. data/doc/ex/get_type_metrics.rb +26 -31
  93. data/doc/ex/gradientfill.rb +12 -12
  94. data/doc/ex/grav.rb +4 -5
  95. data/doc/ex/gravity.rb +61 -60
  96. data/doc/ex/group.rb +16 -16
  97. data/doc/ex/hatchfill.rb +10 -10
  98. data/doc/ex/image.rb +29 -31
  99. data/doc/ex/implode.rb +17 -17
  100. data/doc/ex/level.rb +2 -2
  101. data/doc/ex/level_colors.rb +4 -4
  102. data/doc/ex/line.rb +4 -5
  103. data/doc/ex/line01.rb +11 -13
  104. data/doc/ex/mask.rb +7 -8
  105. data/doc/ex/matte_fill_to_border.rb +2 -3
  106. data/doc/ex/matte_floodfill.rb +2 -3
  107. data/doc/ex/matte_replace.rb +2 -3
  108. data/doc/ex/median_filter.rb +2 -2
  109. data/doc/ex/modulate.rb +2 -2
  110. data/doc/ex/mono.rb +4 -4
  111. data/doc/ex/morph.rb +7 -8
  112. data/doc/ex/mosaic.rb +15 -15
  113. data/doc/ex/motion_blur.rb +2 -2
  114. data/doc/ex/negate.rb +2 -2
  115. data/doc/ex/negate_channel.rb +2 -2
  116. data/doc/ex/nested_rvg.rb +11 -11
  117. data/doc/ex/nonzero.rb +31 -32
  118. data/doc/ex/normalize.rb +2 -2
  119. data/doc/ex/oil_paint.rb +2 -2
  120. data/doc/ex/opacity.rb +3 -3
  121. data/doc/ex/ordered_dither.rb +2 -2
  122. data/doc/ex/path.rb +4 -5
  123. data/doc/ex/pattern1.rb +10 -10
  124. data/doc/ex/pattern2.rb +7 -7
  125. data/doc/ex/polaroid.rb +13 -14
  126. data/doc/ex/polygon.rb +6 -7
  127. data/doc/ex/polygon01.rb +10 -12
  128. data/doc/ex/polyline.rb +4 -5
  129. data/doc/ex/polyline01.rb +11 -13
  130. data/doc/ex/posterize.rb +2 -2
  131. data/doc/ex/preview.rb +3 -4
  132. data/doc/ex/qbezierpath.rb +5 -5
  133. data/doc/ex/quad01.rb +23 -25
  134. data/doc/ex/quantize-m.rb +5 -5
  135. data/doc/ex/radial_blur.rb +2 -2
  136. data/doc/ex/raise.rb +2 -2
  137. data/doc/ex/random_threshold_channel.rb +2 -2
  138. data/doc/ex/rect01.rb +6 -7
  139. data/doc/ex/rect02.rb +9 -11
  140. data/doc/ex/rectangle.rb +3 -4
  141. data/doc/ex/reduce_noise.rb +2 -2
  142. data/doc/ex/remap.rb +7 -8
  143. data/doc/ex/remap_images.rb +9 -11
  144. data/doc/ex/resize_to_fill.rb +3 -5
  145. data/doc/ex/resize_to_fit.rb +3 -5
  146. data/doc/ex/roll.rb +2 -2
  147. data/doc/ex/rotate.rb +6 -7
  148. data/doc/ex/rotate_f.rb +2 -2
  149. data/doc/ex/roundrect.rb +4 -5
  150. data/doc/ex/rubyname.rb +11 -11
  151. data/doc/ex/rvg_clippath.rb +5 -7
  152. data/doc/ex/rvg_linecap.rb +25 -26
  153. data/doc/ex/rvg_linejoin.rb +25 -26
  154. data/doc/ex/rvg_opacity.rb +10 -11
  155. data/doc/ex/rvg_pattern.rb +15 -15
  156. data/doc/ex/rvg_stroke_dasharray.rb +6 -7
  157. data/doc/ex/segment.rb +2 -2
  158. data/doc/ex/sepiatone.rb +2 -3
  159. data/doc/ex/shade.rb +2 -2
  160. data/doc/ex/shadow.rb +2 -3
  161. data/doc/ex/shave.rb +2 -2
  162. data/doc/ex/shear.rb +3 -3
  163. data/doc/ex/sketch.rb +2 -3
  164. data/doc/ex/skewx.rb +4 -5
  165. data/doc/ex/skewy.rb +3 -3
  166. data/doc/ex/smile.rb +113 -113
  167. data/doc/ex/solarize.rb +2 -2
  168. data/doc/ex/sparse_color.rb +29 -30
  169. data/doc/ex/splice.rb +2 -3
  170. data/doc/ex/spread.rb +2 -2
  171. data/doc/ex/stegano.rb +34 -34
  172. data/doc/ex/stroke_dasharray.rb +4 -5
  173. data/doc/ex/stroke_fill.rb +4 -5
  174. data/doc/ex/stroke_linecap.rb +3 -3
  175. data/doc/ex/stroke_linejoin.rb +3 -3
  176. data/doc/ex/stroke_width.rb +3 -3
  177. data/doc/ex/swirl.rb +2 -2
  178. data/doc/ex/text.rb +4 -4
  179. data/doc/ex/text01.rb +7 -8
  180. data/doc/ex/text_align.rb +2 -2
  181. data/doc/ex/text_antialias.rb +9 -10
  182. data/doc/ex/text_styles.rb +11 -13
  183. data/doc/ex/text_undercolor.rb +2 -2
  184. data/doc/ex/texture_fill_to_border.rb +6 -6
  185. data/doc/ex/texture_floodfill.rb +2 -2
  186. data/doc/ex/texturefill.rb +10 -11
  187. data/doc/ex/threshold.rb +2 -2
  188. data/doc/ex/to_blob.rb +4 -5
  189. data/doc/ex/translate.rb +6 -6
  190. data/doc/ex/transparent.rb +5 -5
  191. data/doc/ex/transpose.rb +2 -2
  192. data/doc/ex/transverse.rb +2 -2
  193. data/doc/ex/tref01.rb +11 -12
  194. data/doc/ex/triangle01.rb +5 -6
  195. data/doc/ex/trim.rb +1 -2
  196. data/doc/ex/tspan01.rb +8 -9
  197. data/doc/ex/tspan02.rb +9 -11
  198. data/doc/ex/tspan03.rb +9 -11
  199. data/doc/ex/unsharp_mask.rb +2 -2
  200. data/doc/ex/viewex.rb +19 -21
  201. data/doc/ex/vignette.rb +2 -2
  202. data/doc/ex/watermark.rb +15 -16
  203. data/doc/ex/wave.rb +2 -2
  204. data/doc/ex/wet_floor.rb +17 -18
  205. data/doc/ex/writing_mode01.rb +8 -9
  206. data/doc/ex/writing_mode02.rb +8 -8
  207. data/doc/ilist.html +1 -1
  208. data/doc/usage.html +8 -8
  209. data/examples/constitute.rb +6 -6
  210. data/examples/crop_with_gravity.rb +13 -17
  211. data/examples/demo.rb +305 -305
  212. data/examples/describe.rb +27 -28
  213. data/examples/find_similar_region.rb +16 -16
  214. data/examples/histogram.rb +228 -232
  215. data/examples/identify.rb +165 -167
  216. data/examples/image_opacity.rb +4 -4
  217. data/examples/import_export.rb +10 -10
  218. data/examples/pattern_fill.rb +9 -9
  219. data/examples/rotating_text.rb +13 -14
  220. data/examples/spinner.rb +18 -19
  221. data/examples/thumbnail.rb +13 -14
  222. data/examples/vignette.rb +8 -9
  223. data/ext/RMagick/extconf.rb +489 -367
  224. data/ext/RMagick/rmagick.c +11 -4
  225. data/ext/RMagick/rmagick.h +4 -0
  226. data/ext/RMagick/rmdraw.c +49 -18
  227. data/ext/RMagick/rmenum.c +18 -7
  228. data/ext/RMagick/rmfill.c +4 -1
  229. data/ext/RMagick/rmilist.c +49 -13
  230. data/ext/RMagick/rmimage.c +196 -59
  231. data/ext/RMagick/rminfo.c +33 -10
  232. data/ext/RMagick/rmmain.c +18 -6
  233. data/ext/RMagick/rmmontage.c +12 -4
  234. data/ext/RMagick/rmpixel.c +18 -5
  235. data/ext/RMagick/rmstruct.c +108 -31
  236. data/ext/RMagick/rmutil.c +50 -26
  237. data/lib/rmagick/version.rb +4 -3
  238. data/lib/rmagick_internal.rb +1159 -1176
  239. data/lib/rvg/clippath.rb +36 -39
  240. data/lib/rvg/container.rb +115 -124
  241. data/lib/rvg/deep_equal.rb +46 -50
  242. data/lib/rvg/describable.rb +41 -47
  243. data/lib/rvg/embellishable.rb +384 -410
  244. data/lib/rvg/misc.rb +697 -714
  245. data/lib/rvg/paint.rb +42 -47
  246. data/lib/rvg/pathdata.rb +120 -125
  247. data/lib/rvg/rvg.rb +213 -213
  248. data/lib/rvg/stretchable.rb +159 -162
  249. data/lib/rvg/stylable.rb +109 -117
  250. data/lib/rvg/text.rb +9 -24
  251. data/lib/rvg/transformable.rb +120 -127
  252. data/lib/rvg/units.rb +58 -61
  253. data/rmagick.gemspec +46 -15
  254. data/spec/rmagick/ImageList1_spec.rb +24 -0
  255. data/spec/rmagick/draw_spec.rb +156 -0
  256. data/spec/rmagick/image/blue_shift_spec.rb +16 -0
  257. data/spec/rmagick/image/composite_spec.rb +140 -0
  258. data/spec/rmagick/image/constitute_spec.rb +15 -0
  259. data/spec/rmagick/image/dispatch_spec.rb +18 -0
  260. data/spec/rmagick/image/from_blob_spec.rb +14 -0
  261. data/spec/rmagick/image/ping_spec.rb +14 -0
  262. data/spec/rmagick/image/properties_spec.rb +29 -0
  263. data/spec/spec_helper.rb +4 -0
  264. data/test/Image1.rb +529 -727
  265. data/test/Image2.rb +1291 -1299
  266. data/test/Image3.rb +978 -981
  267. data/test/ImageList1.rb +7 -9
  268. data/test/ImageList2.rb +346 -350
  269. data/test/Image_attributes.rb +683 -686
  270. data/test/Import_Export.rb +25 -30
  271. data/test/Info.rb +339 -343
  272. data/test/Magick.rb +250 -253
  273. data/test/Pixel.rb +107 -110
  274. data/test/Preview.rb +48 -53
  275. data/test/test_all_basic.rb +15 -11
  276. data/test/tmpnam_test.rb +3 -3
  277. data/wercker.yml +10 -0
  278. metadata +77 -10
  279. data/ChangeLog +0 -816
  280. data/lib/RMagick.rb +0 -1
  281. data/test/Draw.rb +0 -121
data/lib/rvg/misc.rb CHANGED
@@ -1,740 +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
- 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
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
36
66
 
37
- end # module Duplicatable
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
38
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
39
94
 
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(', ')})"
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
44
105
  end
45
106
 
46
- def self.convert_to_float(*args)
47
- allow_nil = false
48
- if args.last == :allow_nil
49
- allow_nil = true
50
- args.pop
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
51
117
  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)
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
56
123
  end
57
- return fargs
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
58
149
  end
59
150
 
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})"
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
65
215
  end
66
- return farg
216
+ end
217
+
218
+ @ctx.gc.pop
219
+ [dx, 0]
67
220
  end
221
+ end # class LRTextStrategy
68
222
 
69
- end # class RVG
70
- end # module Magick
223
+ class RLTextStrategy < TextStrategy
224
+ def render(x, y, text)
225
+ fail NotImplementedError
226
+ end
227
+ end # class RLTextStrategy
71
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
72
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
73
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
74
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
75
317
 
76
318
  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
- gm = @ctx.shadow.get_type_metrics("a" + glyph + "a")
108
- gm2 = @ctx.shadow.get_type_metrics("aa")
109
- h = (gm.ascent - gm.descent + 0.5 ).to_i
110
- w = gm.width - gm2.width
111
- if glyph_orientation == 0 || glyph_orientation == 180
112
- [w, h]
113
- else
114
- [h, w]
115
- end
116
- end
117
-
118
- def text_rel_coords(text)
119
- y_rel_coords = []
120
- x_rel_coords = []
121
- first_word = true
122
- words = text.split(::Magick::RVG::WORD_SEP)
123
- words.each do |word|
124
- unless first_word
125
- wx, wy = get_word_spacing()
126
- x_rel_coords << wx
127
- y_rel_coords << wy
128
- end
129
- first_word = false
130
- word.split('').each do |glyph|
131
- wx, wy = get_letter_spacing(glyph)
132
- x_rel_coords << wx
133
- y_rel_coords << wy
134
- end
135
- end
136
- [x_rel_coords, y_rel_coords]
137
- end
138
-
139
- def shift_baseline(glyph_orientation, glyph)
140
- glyph_dimensions = @ctx.shadow.get_type_metrics(glyph)
141
- if glyph_orientation == 0 || glyph_orientation == 180
142
- x = glyph_dimensions.width
143
- else
144
- x = glyph_dimensions.ascent - glyph_dimensions.descent
145
- end
146
- case @ctx.text_attrs.baseline_shift
147
- when :baseline
148
- x = 0
149
- when :sub
150
- ;
151
- when :super
152
- x = -x
153
- when /[-+]?(\d+)%/
154
- m = $1 == '-' ? -1.0 : 1.0
155
- x = (m * x * $1.to_f / 100.0)
156
- else
157
- x = -@ctx.text_attrs.baseline_shift
158
- end
159
- return x
160
- end
161
-
162
- def render_glyph(glyph_orientation, x, y, glyph)
163
- if glyph_orientation == 0
164
- @ctx.gc.text(x, y, enquote(glyph))
165
- else
166
- @ctx.gc.push
167
- @ctx.gc.translate(x, y)
168
- @ctx.gc.rotate(glyph_orientation)
169
- @ctx.gc.translate(-x, -y)
170
- @ctx.gc.text(x, y, enquote(glyph))
171
- @ctx.gc.pop
172
- end
173
- end
174
-
175
- end # class TextStrategy
176
-
177
- class LRTextStrategy < TextStrategy
178
-
179
- def get_word_spacing()
180
- @word_space ||= glyph_metrics(@ctx.text_attrs.glyph_orientation_horizontal, ' ')[0]
181
- [@word_space + @ctx.text_attrs.word_spacing, 0]
182
- end
183
-
184
- def get_letter_spacing(glyph)
185
- gx, gy = glyph_metrics(@ctx.text_attrs.glyph_orientation_horizontal, glyph)
186
- [gx+@ctx.text_attrs.letter_spacing, gy]
187
- end
188
-
189
- def render(x, y, text)
190
- x_rel_coords, y_rel_coords = text_rel_coords(text)
191
- dx = x_rel_coords.inject(0) {|sum, a| sum + a}
192
- dy = y_rel_coords.max
193
-
194
- # We're handling the anchoring.
195
- @ctx.gc.push()
196
- @ctx.gc.text_anchor(Magick::StartAnchor)
197
- if @ctx.text_attrs.text_anchor == :end
198
- x -= dx
199
- elsif @ctx.text_attrs.text_anchor == :middle
200
- x -= dx / 2
201
- end
202
-
203
- # Align the first glyph
204
- case @ctx.text_attrs.glyph_orientation_horizontal
205
- when 0
206
- ;
207
- when 90
208
- y -= dy
209
- when 180
210
- x += x_rel_coords.shift
211
- x_rel_coords << 0
212
- y -= dy
213
- when 270
214
- x += x_rel_coords[0]
215
- end
216
-
217
- y += shift_baseline(@ctx.text_attrs.glyph_orientation_horizontal, text[0,1])
218
-
219
- first_word = true
220
- text.split(::Magick::RVG::WORD_SEP).each do |word|
221
- unless first_word
222
- x += x_rel_coords.shift
223
- end
224
- first_word = false
225
- word.split('').each do |glyph|
226
- render_glyph(@ctx.text_attrs.glyph_orientation_horizontal, x, y, glyph)
227
- x += x_rel_coords.shift
228
- end
229
- end
230
-
231
- @ctx.gc.pop()
232
- [dx, 0]
233
- end
234
-
235
- end # class LRTextStrategy
236
-
237
- class RLTextStrategy < TextStrategy
238
-
239
- def render(x, y, text)
240
- raise NotImplementedError
241
- end
242
-
243
- end # class RLTextStrategy
244
-
245
-
246
- class TBTextStrategy < TextStrategy
247
-
248
- def get_word_spacing()
249
- @word_space ||= glyph_metrics(@ctx.text_attrs.glyph_orientation_vertical, ' ')[1]
250
- [0, @word_space + @ctx.text_attrs.word_spacing]
251
- end
252
-
253
- def get_letter_spacing(glyph)
254
- gx, gy = glyph_metrics(@ctx.text_attrs.glyph_orientation_vertical, glyph)
255
- [gx, gy+@ctx.text_attrs.letter_spacing]
256
- end
257
-
258
- def render(x, y, text)
259
- x_rel_coords, y_rel_coords = text_rel_coords(text)
260
- dx = x_rel_coords.max
261
- dy = y_rel_coords.inject(0) {|sum, a| sum + a}
262
-
263
- # We're handling the anchoring.
264
- @ctx.gc.push()
265
- @ctx.gc.text_anchor(Magick::StartAnchor)
266
- if @ctx.text_attrs.text_anchor == :end
267
- y -= dy
268
- elsif @ctx.text_attrs.text_anchor == :middle
269
- y -= dy / 2
270
- end
271
-
272
- # Align the first glyph such that its center
273
- # is aligned on x and its top is aligned on y.
274
-
275
- case @ctx.text_attrs.glyph_orientation_vertical
276
- when 0
277
- x -= x_rel_coords.max / 2
278
- y += y_rel_coords[0]
279
- when 90
280
- x -= x_rel_coords.max / 2
281
- when 180
282
- x += x_rel_coords.max / 2
283
- when 270
284
- x += x_rel_coords.max / 2
285
- y += y_rel_coords.shift
286
- y_rel_coords << 0 # since we used an element we need to add a dummy
287
- end
288
-
289
- x -= shift_baseline(@ctx.text_attrs.glyph_orientation_vertical, text[0,1])
290
-
291
- first_word = true
292
- text.split(::Magick::RVG::WORD_SEP).each do |word|
293
- unless first_word
294
- y += y_rel_coords.shift
295
- x_rel_coords.shift
296
- end
297
- first_word = false
298
- word.split('').each do |glyph|
299
- case @ctx.text_attrs.glyph_orientation_vertical.to_i
300
- when 0, 90, 270
301
- x_shift = (dx - x_rel_coords.shift) / 2
302
- when 180
303
- x_shift = -(dx - x_rel_coords.shift) / 2
304
- end
305
-
306
- render_glyph(@ctx.text_attrs.glyph_orientation_vertical, x+x_shift, y, glyph)
307
- y += y_rel_coords.shift
308
- end
309
- end
310
-
311
- @ctx.gc.pop()
312
- [0, dy]
313
- end
314
-
315
- end # class TBTextStrategy
316
-
317
- # Handle "easy" text
318
- class DefaultTextStrategy < TextStrategy
319
-
320
- def render(x, y, text)
321
- @ctx.gc.text(x, y, enquote(text))
322
- tm = @ctx.shadow.get_type_metrics(text)
323
- dx = case @ctx.text_attrs.text_anchor
324
- when :start
325
- tm.width
326
- when :middle
327
- tm.width / 2
328
- when :end
329
- 0
330
- end
331
- [dx, 0]
332
- end
333
-
334
- end # class NormalTextStrategy
335
-
336
- end # class Utility
337
- end # class RVG
338
- 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
339
344
 
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
340
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
341
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
342
375
 
343
- module Magick
344
- class RVG
345
- class Utility
346
-
347
- class TextAttributes
348
-
349
- public
350
-
351
- WRITING_MODE = %w{lr-tb lr rl-tb rl tb-rl tb}
352
-
353
- def initialize()
354
- @affine = Array.new
355
- @affine << Magick::AffineMatrix.new(1, 0, 0, 1, 0, 0)
356
- @baseline_shift = Array.new
357
- @baseline_shift << :baseline
358
- @glyph_orientation_horizontal = Array.new
359
- @glyph_orientation_horizontal << 0
360
- @glyph_orientation_vertical = Array.new
361
- @glyph_orientation_vertical << 90
362
- @letter_spacing = Array.new
363
- @letter_spacing << 0
364
- @text_anchor = Array.new
365
- @text_anchor << :start
366
- @word_spacing = Array.new
367
- @word_spacing << 0
368
- @writing_mode = Array.new
369
- @writing_mode << 'lr-tb'
370
- end
371
-
372
- def push()
373
- @affine.push(@affine.last.dup)
374
- @baseline_shift.push(@baseline_shift.last)
375
- @text_anchor.push(@text_anchor.last)
376
- @writing_mode.push(@writing_mode.last.dup)
377
- @glyph_orientation_vertical.push(@glyph_orientation_vertical.last)
378
- @glyph_orientation_horizontal.push(@glyph_orientation_horizontal.last)
379
- @letter_spacing.push(@letter_spacing.last)
380
- @word_spacing.push(@word_spacing.last)
381
- end
382
-
383
- def pop()
384
- @affine.pop
385
- @baseline_shift.pop
386
- @text_anchor.pop
387
- @writing_mode.pop
388
- @glyph_orientation_vertical.pop
389
- @glyph_orientation_horizontal.pop
390
- @letter_spacing.pop
391
- @word_spacing.pop
392
- end
393
-
394
- def set_affine(sx, rx, ry, sy, tx, ty)
395
- @affine[-1].sx = sx
396
- @affine[-1].rx = rx
397
- @affine[-1].ry = ry
398
- @affine[-1].sy = sy
399
- @affine[-1].tx = tx
400
- @affine[-1].ty = ty
401
- end
402
-
403
- def affine()
404
- @affine[-1]
405
- end
406
-
407
- def baseline_shift()
408
- @baseline_shift[-1]
409
- end
410
-
411
- def baseline_shift=(value)
412
- @baseline_shift[-1] = value
413
- end
414
-
415
- def text_anchor()
416
- @text_anchor[-1]
417
- end
418
-
419
- def text_anchor=(anchor)
420
- @text_anchor[-1] = anchor
421
- end
422
-
423
- def glyph_orientation_vertical()
424
- @glyph_orientation_vertical[-1]
425
- end
426
-
427
- def glyph_orientation_vertical=(angle)
428
- @glyph_orientation_vertical[-1] = angle
429
- end
430
-
431
- def glyph_orientation_horizontal()
432
- @glyph_orientation_horizontal[-1]
433
- end
434
-
435
- def glyph_orientation_horizontal=(angle)
436
- @glyph_orientation_horizontal[-1] = angle
437
- end
438
-
439
- def letter_spacing()
440
- @letter_spacing[-1]
441
- end
442
-
443
- def letter_spacing=(value)
444
- @letter_spacing[-1] = value
445
- end
446
-
447
- def non_default?
448
- @baseline_shift[-1] != :baseline || @letter_spacing[-1] != 0 ||
449
- @word_spacing[-1] != 0 || @writing_mode[-1][/\Alr/].nil? ||
450
- @glyph_orientation_horizontal[-1] != 0
451
- end
452
-
453
- def word_spacing()
454
- @word_spacing[-1]
455
- end
456
-
457
- def word_spacing=(value)
458
- @word_spacing[-1] = value
459
- end
460
-
461
- def writing_mode()
462
- @writing_mode[-1]
463
- end
464
-
465
- def writing_mode=(mode)
466
- @writing_mode[-1] = WRITING_MODE.include?(mode) ? mode : 'lr-tb'
467
- end
468
-
469
- end # class TextAttributes
470
-
471
- class GraphicContext
472
-
473
- FONT_STRETCH = {:normal => Magick::NormalStretch,
474
- :ultra_condensed => Magick::UltraCondensedStretch,
475
- :extra_condensed => Magick::ExtraCondensedStretch,
476
- :condensed => Magick::CondensedStretch,
477
- :semi_condensed => Magick::SemiCondensedStretch,
478
- :semi_expanded => Magick::SemiExpandedStretch,
479
- :expanded => Magick::ExpandedStretch,
480
- :extra_expanded => Magick::ExtraExpandedStretch,
481
- :ultra_expanded => Magick::UltraExpandedStretch}
482
-
483
- FONT_STYLE = {:normal => Magick::NormalStyle,
484
- :italic => Magick::ItalicStyle,
485
- :oblique => Magick::ObliqueStyle}
486
-
487
- FONT_WEIGHT = {'normal' => Magick::NormalWeight,
488
- 'bold' => Magick::BoldWeight,
489
- 'bolder' => Magick::BolderWeight,
490
- 'lighter' => Magick::LighterWeight}
491
-
492
- TEXT_ANCHOR = {:start => Magick::StartAnchor,
493
- :middle => Magick::MiddleAnchor,
494
- :end => Magick::EndAnchor}
495
-
496
- ANCHOR_TO_ALIGN = {:start => Magick::LeftAlign,
497
- :middle => Magick::CenterAlign,
498
- :end => Magick::RightAlign}
499
-
500
- TEXT_DECORATION = {:none => Magick::NoDecoration,
501
- :underline => Magick::UnderlineDecoration,
502
- :overline => Magick::OverlineDecoration,
503
- :line_through => Magick::LineThroughDecoration}
504
-
505
- TEXT_STRATEGIES = {'lr-tb'=>LRTextStrategy, 'lr'=>LRTextStrategy,
506
- 'rt-tb'=>RLTextStrategy, 'rl'=>RLTextStrategy,
507
- 'tb-rl'=>TBTextStrategy, 'tb'=>TBTextStrategy}
508
-
509
- def GraphicContext.degrees_to_radians(deg)
510
- Math::PI * (deg % 360.0) / 180.0
511
- end
512
-
513
- private
514
-
515
- def init_matrix()
516
- @rx = @ry = 0
517
- @sx = @sy = 1
518
- @tx = @ty = 0
519
- end
520
-
521
- def concat_matrix()
522
- curr = @text_attrs.affine
523
- sx = curr.sx * @sx + curr.ry * @rx
524
- rx = curr.rx * @sx + curr.sy * @rx
525
- ry = curr.sx * @ry + curr.ry * @sy
526
- sy = curr.rx * @ry + curr.sy * @sy
527
- tx = curr.sx * @tx + curr.ry * @ty + curr.tx
528
- ty = curr.rx * @tx + curr.sy * @ty + curr.ty
529
- @text_attrs.set_affine(sx, rx, ry, sy, tx, ty)
530
- init_matrix()
531
- end
532
-
533
- public
534
-
535
- attr_reader :gc, :text_attrs
536
-
537
- def initialize()
538
- @gc = Magick::Draw.new
539
- @shadow = Array.new
540
- @shadow << Magick::Draw.new
541
- @text_attrs = TextAttributes.new
542
- init_matrix()
543
- end
544
-
545
- def method_missing(methID, *args, &block)
546
- @gc.__send__(methID, *args, &block)
547
- end
548
-
549
- def affine(sx, rx, ry, sy, tx, ty)
550
- sx, rx, ry, sy, tx, ty = Magick::RVG.convert_to_float(sx, rx, ry, sy, tx, ty)
551
- @gc.affine(sx, rx, ry, sy, tx, ty)
552
- @text_attrs.set_affine(sx, rx, ry, sy, tx, ty)
553
- nil
554
- end
555
-
556
- def baseline_shift(value)
557
- @text_attrs.baseline_shift = case value
558
- when 'baseline', 'sub', 'super'
559
- value.intern
560
- when /[-+]?\d+%/, Numeric
561
- value
562
- else
563
- :baseline
564
- end
565
- nil
566
- end
567
-
568
- def font(name)
569
- @gc.font(name)
570
- @shadow[-1].font = name
571
- nil
572
- end
573
-
574
- def font_family(name)
575
- @gc.font_family(name)
576
- @shadow[-1].font_family = name
577
- nil
578
- end
579
-
580
- def font_size(points)
581
- @gc.font_size(points)
582
- @shadow[-1].pointsize = points
583
- nil
584
- end
585
-
586
- def font_stretch(stretch)
587
- stretch = FONT_STRETCH.fetch(stretch.intern, Magick::NormalStretch)
588
- @gc.font_stretch(stretch)
589
- @shadow[-1].font_stretch = stretch
590
- nil
591
- end
592
-
593
- def font_style(style)
594
- style = FONT_STYLE.fetch(style.intern, Magick::NormalStyle)
595
- @gc.font_style(style)
596
- @shadow[-1].font_style = style
597
- nil
598
- end
599
-
600
- def font_weight(weight)
601
- # If the arg is not in the hash use it directly. Handles numeric values.
602
- weight = FONT_WEIGHT.fetch(weight) {|key| key}
603
- @gc.font_weight(weight)
604
- @shadow[-1].font_weight = weight
605
- nil
606
- end
607
-
608
- def glyph_orientation_horizontal(deg)
609
- deg = Magick::RVG.convert_one_to_float(deg)
610
- @text_attrs.glyph_orientation_horizontal = (deg % 360) / 90 * 90
611
- nil
612
- end
613
-
614
- def glyph_orientation_vertical(deg)
615
- deg = Magick::RVG.convert_one_to_float(deg)
616
- @text_attrs.glyph_orientation_vertical = (deg % 360) / 90 * 90
617
- nil
618
- end
619
-
620
- def inspect()
621
- @gc.inspect
622
- end
623
-
624
- def letter_spacing(value)
625
- @text_attrs.letter_spacing = Magick::RVG.convert_one_to_float(value)
626
- nil
627
- end
628
-
629
- def push()
630
- @gc.push
631
- @shadow.push(@shadow.last.dup)
632
- @text_attrs.push
633
- nil
634
- end
635
-
636
- def pop()
637
- @gc.pop
638
- @shadow.pop
639
- @text_attrs.pop
640
- nil
641
- end
642
-
643
- def rotate(degrees)
644
- degrees = Magick::RVG.convert_one_to_float(degrees)
645
- @gc.rotate(degrees)
646
- @sx = Math.cos(GraphicContext.degrees_to_radians(degrees))
647
- @rx = Math.sin(GraphicContext.degrees_to_radians(degrees))
648
- @ry = -Math.sin(GraphicContext.degrees_to_radians(degrees))
649
- @sy = Math.cos(GraphicContext.degrees_to_radians(degrees))
650
- concat_matrix()
651
- nil
652
- end
653
-
654
- def scale(sx, sy)
655
- sx, sy = Magick::RVG.convert_to_float(sx, sy)
656
- @gc.scale(sx, sy)
657
- @sx, @sy = sx, sy
658
- concat_matrix()
659
- nil
660
- end
661
-
662
- def shadow()
663
- @shadow.last
664
- end
665
-
666
- def skewX(degrees)
667
- degrees = Magick::RVG.convert_one_to_float(degrees)
668
- @gc.skewX(degrees)
669
- @ry = Math.tan(GraphicContext.degrees_to_radians(degrees))
670
- concat_matrix()
671
- nil
672
- end
673
-
674
- def skewY(degrees)
675
- degrees = Magick::RVG.convert_one_to_float(degrees)
676
- @gc.skewY(degrees)
677
- @rx = Math.tan(GraphicContext.degrees_to_radians(degrees))
678
- concat_matrix()
679
- nil
680
- end
681
-
682
- def stroke_width(width)
683
- width = Magick::RVG.convert_one_to_float(width)
684
- @gc.stroke_width(width)
685
- @shadow[-1].stroke_width = width
686
- nil
687
- end
688
-
689
- def text(x, y, text)
690
- return if text.length == 0
691
- if @text_attrs.non_default?
692
- text_renderer = TEXT_STRATEGIES[@text_attrs.writing_mode].new(self)
693
- else
694
- text_renderer = DefaultTextStrategy.new(self)
695
- end
696
-
697
- return text_renderer.render(x, y, text)
698
- end
699
-
700
- def text_anchor(anchor)
701
- anchor = anchor.intern
702
- anchor_enum = TEXT_ANCHOR.fetch(anchor, Magick::StartAnchor)
703
- @gc.text_anchor(anchor_enum)
704
- align = ANCHOR_TO_ALIGN.fetch(anchor, Magick::LeftAlign)
705
- @shadow[-1].align = align
706
- @text_attrs.text_anchor = anchor
707
- nil
708
- end
709
-
710
- def text_decoration(decoration)
711
- decoration = TEXT_DECORATION.fetch(decoration.intern, Magick::NoDecoration)
712
- @gc.decorate(decoration)
713
- @shadow[-1].decorate = decoration
714
- nil
715
- end
716
-
717
- def translate(tx, ty)
718
- tx, ty = Magick::RVG.convert_to_float(tx, ty)
719
- @gc.translate(tx, ty)
720
- @tx, @ty = tx, ty
721
- concat_matrix()
722
- nil
723
- end
724
-
725
- def word_spacing(value)
726
- @text_attrs.word_spacing = Magick::RVG.convert_one_to_float(value)
727
- nil
728
- end
729
-
730
- def writing_mode(mode)
731
- @text_attrs.writing_mode = mode
732
- nil
733
- end
734
-
735
- end # class GraphicContext
736
-
737
- end # class Utility
738
- end # class RVG
739
- end # module Magick
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
740
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
723
+ end # module Magick