gd2-ffij 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/.gitignore +5 -0
  2. data/COPYING +340 -0
  3. data/COPYRIGHT +17 -0
  4. data/README +34 -0
  5. data/Rakefile +32 -0
  6. data/gd2-ffij.gemspec +96 -0
  7. data/lib/gd2-ffij.rb +209 -0
  8. data/lib/gd2/canvas.rb +422 -0
  9. data/lib/gd2/color.rb +240 -0
  10. data/lib/gd2/ffi_struct.rb +76 -0
  11. data/lib/gd2/font.rb +347 -0
  12. data/lib/gd2/image.rb +785 -0
  13. data/lib/gd2/palette.rb +253 -0
  14. data/test/canvas_test.rb +186 -0
  15. data/test/image_test.rb +149 -0
  16. data/test/images/test.bmp +0 -0
  17. data/test/images/test.gd +0 -0
  18. data/test/images/test.gd2 +0 -0
  19. data/test/images/test.gif +0 -0
  20. data/test/images/test.jpg +0 -0
  21. data/test/images/test.png +0 -0
  22. data/test/images/test.wbmp +0 -0
  23. data/test/images/test.xbm +686 -0
  24. data/test/images/test.xcf +0 -0
  25. data/test/images/test.xpm +261 -0
  26. data/test/images/test_arc.gd2 +0 -0
  27. data/test/images/test_canvas_filled_polygon.gd2 +0 -0
  28. data/test/images/test_canvas_filled_rectangle.gd2 +0 -0
  29. data/test/images/test_canvas_line.gd2 +0 -0
  30. data/test/images/test_canvas_move_to_and_line_to.gd2 +0 -0
  31. data/test/images/test_canvas_polygon.gd2 +0 -0
  32. data/test/images/test_canvas_rectangle.gd2 +0 -0
  33. data/test/images/test_circle.gd2 +0 -0
  34. data/test/images/test_color.gd2 +0 -0
  35. data/test/images/test_color.png +0 -0
  36. data/test/images/test_color.xcf +0 -0
  37. data/test/images/test_color_indexed.gd2 +0 -0
  38. data/test/images/test_color_sharpened.gd2 +0 -0
  39. data/test/images/test_cropped.gd2 +0 -0
  40. data/test/images/test_ellipse.gd2 +0 -0
  41. data/test/images/test_fill.gd2 +0 -0
  42. data/test/images/test_fill_to.gd2 +0 -0
  43. data/test/images/test_filled_circle.gd2 +0 -0
  44. data/test/images/test_filled_ellipse.gd2 +0 -0
  45. data/test/images/test_filled_wedge.gd2 +0 -0
  46. data/test/images/test_polar_transform.gd2 +0 -0
  47. data/test/images/test_resampled.gd2 +0 -0
  48. data/test/images/test_resized.gd2 +0 -0
  49. data/test/images/test_rotated_180.gd2 +0 -0
  50. data/test/images/test_text.gd2 +0 -0
  51. data/test/images/test_text_circle.gd2 +0 -0
  52. data/test/images/test_wedge.gd2 +0 -0
  53. data/test/test_helper.rb +13 -0
  54. data/vendor/fonts/ttf/DejaVuSans.ttf +0 -0
  55. data/vendor/fonts/ttf/LICENSE +99 -0
  56. metadata +118 -0
data/lib/gd2/color.rb ADDED
@@ -0,0 +1,240 @@
1
+ #
2
+ # Ruby/GD2 -- Ruby binding for gd 2 graphics library
3
+ #
4
+ # Copyright © 2005 Robert Leslie, 2010 J Smith
5
+ #
6
+ # This file is part of Ruby/GD2.
7
+ #
8
+ # Ruby/GD2 is free software; you can redistribute it and/or modify it under
9
+ # the terms of the GNU General Public License as published by the Free
10
+ # Software Foundation; either version 2 of the License, or (at your option)
11
+ # any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful, but
14
+ # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15
+ # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16
+ # for more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public License along
19
+ # with this program; if not, write to the Free Software Foundation, Inc.,
20
+ # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21
+ #
22
+
23
+ module GD2
24
+ #
25
+ # = Description
26
+ #
27
+ # Color objects hold the red, green, blue, and alpha components for a pixel
28
+ # color. Color objects may also be linked to a particular palette index
29
+ # associated with an image.
30
+ #
31
+ # == Creating
32
+ #
33
+ # Color objects are created by specifying the individual components:
34
+ #
35
+ # red = Color[1.0, 0.0, 0.0]
36
+ # green = Color[0.0, 1.0, 0.0]
37
+ # blue = Color[0.0, 0.0, 1.0]
38
+ #
39
+ # transparent_yellow = Color[1.0, 1.0, 0.0, 0.5]
40
+ #
41
+ # The components may be specified as a percentage, as an explicit value
42
+ # between 0..RGB_MAX or 0..ALPHA_MAX, or as another color from which the
43
+ # associated component will be extracted.
44
+ #
45
+ class Color
46
+ attr_reader :rgba #:nodoc:
47
+
48
+ # The palette index of this color, if associated with an image palette
49
+ attr_reader :index
50
+
51
+ # The palette of this color, if associated with an image palette
52
+ attr_reader :palette
53
+
54
+ alias to_i rgba
55
+
56
+ def self.normalize(value, max, component = nil) #:nodoc:
57
+ case value
58
+ when Integer
59
+ (value < 0) ? 0 : (value > max) ? max : value
60
+ when Float
61
+ normalize((value * max).round, max, component)
62
+ when Color
63
+ value.send(component)
64
+ else
65
+ return normalize(value.to_i, max) if value.respond_to?(:to_i)
66
+ raise TypeError
67
+ end
68
+ end
69
+
70
+ def self.pack(r, g, b, a) #:nodoc:
71
+ r = r.read_int if r.is_a?(FFI::Pointer)
72
+ g = g.read_int if g.is_a?(FFI::Pointer)
73
+ b = b.read_int if b.is_a?(FFI::Pointer)
74
+ a = a.read_int if a.is_a?(FFI::Pointer)
75
+ (a << 24) + (r << 16) + (g << 8) + b
76
+ end
77
+
78
+ class << self
79
+ alias [] new
80
+ end
81
+
82
+ # Create a new Color object with the given component values.
83
+ def initialize(r, g, b, a = ALPHA_OPAQUE)
84
+ r = self.class.normalize(r, RGB_MAX, :red)
85
+ g = self.class.normalize(g, RGB_MAX, :green)
86
+ b = self.class.normalize(b, RGB_MAX, :blue)
87
+ a = self.class.normalize(a, ALPHA_MAX, :alpha)
88
+
89
+ init_with_rgba(self.class.pack(r, g, b, a))
90
+ end
91
+
92
+ def self.new_from_rgba(rgba) #:nodoc:
93
+ allocate.init_with_rgba(rgba)
94
+ end
95
+
96
+ def self.new_from_palette(r, g, b, a, index, palette) #:nodoc:
97
+ allocate.init_with_rgba(pack(r, g, b, a), index, palette)
98
+ end
99
+
100
+ def init_with_rgba(rgba, index = nil, palette = nil) #:nodoc:
101
+ @rgba = rgba
102
+ @index = index
103
+ @palette = palette
104
+ self
105
+ end
106
+
107
+ BLACK = Color[0.0, 0.0, 0.0].freeze
108
+ WHITE = Color[1.0, 1.0, 1.0].freeze
109
+ TRANSPARENT = Color[0.0, 0.0, 0.0, ALPHA_TRANSPARENT].freeze
110
+
111
+ # Return *true* if this color is associated with the specified palette,
112
+ # or with any palette if *nil* is given.
113
+ def from_palette?(palette = nil)
114
+ @palette && @index && (palette.nil? || palette.equal?(@palette))
115
+ end
116
+
117
+ # Return a string description of this color.
118
+ def to_s
119
+ s = 'RGB'
120
+ s += "A" if alpha != ALPHA_OPAQUE
121
+ s += "[#{@index}]" if @index
122
+ s += '#' + [red, green, blue].map { |e| '%02X' % e }.join('')
123
+ s += '%02X' % alpha if alpha != ALPHA_OPAQUE
124
+ s
125
+ end
126
+
127
+ def inspect #:nodoc:
128
+ "<#{to_s}>"
129
+ end
130
+
131
+ # Compare this color with another color. Returns *true* if the associated
132
+ # red, green, blue, and alpha components are identical.
133
+ def ==(other)
134
+ other.kind_of?(Color) && rgba == other.rgba
135
+ end
136
+
137
+ # Return *true* if this color is visually identical to another color.
138
+ def ===(other)
139
+ self == other || (self.transparent? && other.transparent?)
140
+ end
141
+
142
+ # Compare this color with another color in a manner that takes into account
143
+ # palette identities.
144
+ def eql?(other)
145
+ self == other &&
146
+ (palette.nil? || other.palette.nil? ||
147
+ (palette == other.palette && index == other.index))
148
+ end
149
+
150
+ def hash #:nodoc:
151
+ rgba.hash
152
+ end
153
+
154
+ def rgba=(value) #:nodoc:
155
+ @rgba = value
156
+ @palette[@index] = self if from_palette?
157
+ end
158
+
159
+ # Return the red component of this color (0..RGB_MAX).
160
+ def red
161
+ (rgba & 0xFF0000) >> 16
162
+ end
163
+ alias r red
164
+
165
+ # Modify the red component of this color. If this color is associated
166
+ # with a palette entry, this also modifies the palette.
167
+ def red=(value)
168
+ self.rgba = (rgba & ~0xFF0000) |
169
+ (self.class.normalize(value, RGB_MAX, :red) << 16)
170
+ end
171
+ alias r= red=
172
+
173
+ # Return the green component of this color (0..RGB_MAX).
174
+ def green
175
+ (rgba & 0x00FF00) >> 8
176
+ end
177
+ alias g green
178
+
179
+ # Modify the green component of this color. If this color is associated
180
+ # with a palette entry, this also modifies the palette.
181
+ def green=(value)
182
+ self.rgba = (rgba & ~0x00FF00) |
183
+ (self.class.normalize(value, RGB_MAX, :green) << 8)
184
+ end
185
+ alias g= green=
186
+
187
+ # Return the blue component of this color (0..RGB_MAX).
188
+ def blue
189
+ rgba & 0x0000FF
190
+ end
191
+ alias b blue
192
+
193
+ # Modify the blue component of this color. If this color is associated
194
+ # with a palette entry, this also modifies the palette.
195
+ def blue=(value)
196
+ self.rgba = (rgba & ~0x0000FF) |
197
+ self.class.normalize(value, RGB_MAX, :blue)
198
+ end
199
+ alias b= blue=
200
+
201
+ # Return the alpha component of this color (0..ALPHA_MAX).
202
+ def alpha
203
+ (rgba & 0x7F000000) >> 24
204
+ end
205
+ alias a alpha
206
+
207
+ # Modify the alpha component of this color. If this color is associated
208
+ # with a palette entry, this also modifies the palette.
209
+ def alpha=(value)
210
+ self.rgba = (rgba & ~0xFF000000) |
211
+ (self.class.normalize(value, ALPHA_MAX, :alpha) << 24)
212
+ end
213
+ alias a= alpha=
214
+
215
+ # Alpha blend this color with the given color. If this color is associated
216
+ # with a palette entry, this also modifies the palette.
217
+ def alpha_blend!(other)
218
+ self.rgba = GD2FFI.send(:gdAlphaBlend, rgba, other.rgba)
219
+ self
220
+ end
221
+ alias << alpha_blend!
222
+
223
+ # Like Color#alpha_blend! except returns a new Color without modifying
224
+ # the receiver.
225
+ def alpha_blend(other)
226
+ dup.alpha_blend!(other)
227
+ end
228
+
229
+ # Return *true* if the alpha channel of this color is completely
230
+ # transparent.
231
+ def transparent?
232
+ alpha == ALPHA_TRANSPARENT
233
+ end
234
+
235
+ # Return *true* if the alpha channel of this color is completely opaque.
236
+ def opaque?
237
+ alpha == ALPHA_OPAQUE
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,76 @@
1
+ #
2
+ # Ruby/GD2 -- Ruby binding for gd 2 graphics library
3
+ #
4
+ # Copyright � 2010 J Smith
5
+ #
6
+ # This file is part of Ruby/GD2.
7
+ #
8
+ # Ruby/GD2 is free software; you can redistribute it and/or modify it under
9
+ # the terms of the GNU General Public License as published by the Free
10
+ # Software Foundation; either version 2 of the License, or (at your option)
11
+ # any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful, but
14
+ # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15
+ # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16
+ # for more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public License along
19
+ # with this program; if not, write to the Free Software Foundation, Inc.,
20
+ # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21
+ #
22
+
23
+ module GD2::FFIStruct
24
+ class FTStringExtraPtr < FFI::Struct
25
+ layout(
26
+ :flags, :int,
27
+ :linespacing, :double,
28
+ :charmap, :int,
29
+ :hdpi, :int,
30
+ :vdpi, :int,
31
+ :xshow, :pointer, # char*
32
+ :fontpath, :pointer # char*
33
+ )
34
+ end
35
+
36
+ class ImagePtr < FFI::ManagedStruct
37
+ layout(
38
+ :pixels, :pointer, # unsigned char**
39
+ :sx, :int,
40
+ :sy, :int,
41
+ :colorsTotal, :int,
42
+ :red, [ :int, 256 ],
43
+ :green, [ :int, 256 ],
44
+ :blue, [ :int, 256 ],
45
+ :open, [ :int, 256 ],
46
+ :transparent, :int,
47
+ :polyInts, :pointer, # int*
48
+ :polyAllocated, :int,
49
+ :brush, :pointer, # gdImageStruct*
50
+ :tile, :pointer, # gdImageStruct*
51
+ :brushColorMap, [ :int, 256 ],
52
+ :tileColorMap, [ :int, 256 ],
53
+ :styleLength, :int,
54
+ :stylePos, :int,
55
+ :style, :pointer, # int*,
56
+ :interlace, :int,
57
+ :thick, :int,
58
+ :alpha, [ :int, 256 ],
59
+ :trueColor, :int,
60
+ :tpixels, :pointer, # int**
61
+ :alphaBlendingFlag, :int,
62
+ :saveAlphaFlag, :int,
63
+ :aa, :int,
64
+ :aa_color, :int,
65
+ :aa_dont_blend, :int,
66
+ :cx1, :int,
67
+ :cy1, :int,
68
+ :cx2, :int,
69
+ :cy2, :int
70
+ )
71
+
72
+ def self.release(ptr)
73
+ GD2FFI.gdImageDestroy(ptr)
74
+ end
75
+ end
76
+ end
data/lib/gd2/font.rb ADDED
@@ -0,0 +1,347 @@
1
+ #
2
+ # Ruby/GD2 -- Ruby binding for gd 2 graphics library
3
+ #
4
+ # Copyright © 2005 Robert Leslie, 2010 J Smith
5
+ #
6
+ # This file is part of Ruby/GD2.
7
+ #
8
+ # Ruby/GD2 is free software; you can redistribute it and/or modify it under
9
+ # the terms of the GNU General Public License as published by the Free
10
+ # Software Foundation; either version 2 of the License, or (at your option)
11
+ # any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful, but
14
+ # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15
+ # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16
+ # for more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public License along
19
+ # with this program; if not, write to the Free Software Foundation, Inc.,
20
+ # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21
+ #
22
+
23
+ module GD2
24
+ #
25
+ # = Description
26
+ #
27
+ # Font objects represent a particular font in a particular size.
28
+ #
29
+ # == Built-in Fonts
30
+ #
31
+ # The following font classes may be used without further instantiation:
32
+ #
33
+ # Font::Tiny
34
+ # Font::Small
35
+ # Font::MediumBold
36
+ # Font::Large
37
+ # Font::Giant
38
+ #
39
+ # == TrueType Fonts
40
+ #
41
+ # To use a TrueType font, first instantiate the font at a particular size:
42
+ #
43
+ # font = Font::TrueType[fontname, ptsize]
44
+ #
45
+ # Here +fontname+ may be a path to a TrueType font, or a fontconfig pattern
46
+ # if fontconfig support is enabled (see Font::TrueType.fontconfig).
47
+ #
48
+ # See Font::TrueType.new for further options.
49
+ class Font
50
+ private_class_method :new
51
+
52
+ def self.font_ptr #:nodoc:
53
+ GD2FFI.send(font_sym)
54
+ end
55
+
56
+ def self.draw(image_ptr, x, y, angle, string, fg) #:nodoc:
57
+ raise ArgumentError, "Angle #{angle} not supported for #{self}" unless
58
+ angle == 0.degrees || angle == 90.degrees
59
+
60
+ GD2FFI.send(angle > 0 ? :gdImageStringUp : :gdImageString, image_ptr,
61
+ font_ptr, x, y, string, fg)
62
+ nil
63
+ end
64
+ end
65
+
66
+ class Font::Small < Font
67
+ def self.font_sym #:nodoc:
68
+ :gdFontGetSmall
69
+ end
70
+ end
71
+
72
+ class Font::Large < Font
73
+ def self.font_sym #:nodoc:
74
+ :gdFontGetLarge
75
+ end
76
+ end
77
+
78
+ class Font::MediumBold < Font
79
+ def self.font_sym #:nodoc:
80
+ :gdFontGetMediumBold
81
+ end
82
+ end
83
+
84
+ class Font::Giant < Font
85
+ def self.font_sym #:nodoc:
86
+ :gdFontGetGiant
87
+ end
88
+ end
89
+
90
+ class Font::Tiny < Font
91
+ def self.font_sym #:nodoc:
92
+ :gdFontGetTiny
93
+ end
94
+ end
95
+
96
+ class Font::TrueType
97
+ class FontconfigError < StandardError; end
98
+ class FreeTypeError < StandardError; end
99
+
100
+ CHARMAP_UNICODE = 0
101
+ CHARMAP_SHIFT_JIS = 1
102
+ CHARMAP_BIG5 = 2
103
+
104
+ FTEX_LINESPACE = 1
105
+ FTEX_CHARMAP = 2
106
+ FTEX_RESOLUTION = 4
107
+ FTEX_DISABLE_KERNING = 8
108
+ FTEX_XSHOW = 16
109
+ FTEX_FONTPATHNAME = 32
110
+ FTEX_FONTCONFIG = 64
111
+ FTEX_RETURNFONTPATHNAME = 128
112
+
113
+ @@fontcount = 0
114
+ @@fontconfig = false
115
+
116
+ def self.register(font) #:nodoc:
117
+ Thread.critical = true
118
+
119
+ count = @@fontcount
120
+ @@fontcount += 1
121
+
122
+ if count.zero?
123
+ raise FreeTypeError, 'FreeType library failed to initialize' unless
124
+ GD2FFI.send(:gdFontCacheSetup).zero?
125
+ end
126
+
127
+ ObjectSpace.define_finalizer(font, font_finalizer)
128
+ ensure
129
+ Thread.critical = false
130
+ end
131
+
132
+ def self.font_finalizer
133
+ proc { unregister }
134
+ end
135
+
136
+ def self.unregister
137
+ Thread.critical = true
138
+
139
+ @@fontcount -= 1
140
+ GD2FFI.send(:gdFontCacheShutdown) if @@fontcount.zero?
141
+ ensure
142
+ Thread.critical = false
143
+ end
144
+
145
+ private_class_method :font_finalizer, :unregister
146
+
147
+ def self.fontconfig #:nodoc:
148
+ @@fontconfig
149
+ end
150
+
151
+ # Return a boolean indicating whether fontconfig support has been enabled.
152
+ # The default is *false*.
153
+ def self.fontconfig?
154
+ fontconfig
155
+ end
156
+
157
+ # Set whether fontconfig support should be enabled. To use this, the GD
158
+ # library must have been built with fontconfig support. Raises an error if
159
+ # fontconfig support is unavailable.
160
+ def self.fontconfig=(want)
161
+ avail = !GD2FFI.send(:gdFTUseFontConfig, want ? 1 : 0).zero?
162
+ raise FontconfigError, 'Fontconfig not available' if want && !avail
163
+ @@fontconfig = want
164
+ end
165
+
166
+ class << self
167
+ alias [] new
168
+ end
169
+
170
+ # The effective path to this TrueType font
171
+ attr_reader :fontpath
172
+
173
+ # The chosen linespacing
174
+ attr_reader :linespacing
175
+
176
+ # The chosen charmap
177
+ attr_reader :charmap
178
+
179
+ # The chosen horizontal resolution hint
180
+ attr_reader :hdpi
181
+
182
+ # The chosen vertical resolution hint
183
+ attr_reader :vdpi
184
+
185
+ # Whether kerning is desired
186
+ attr_reader :kerning
187
+
188
+ # Instantiate a TrueType font given by +fontname+ (either a pathname or a
189
+ # fontconfig pattern if fontconfig is enabled) and +ptsize+ (a point size
190
+ # given as a floating point number).
191
+ #
192
+ # The possible +options+ are:
193
+ #
194
+ # - :linespacing => The desired line spacing for multiline text, expressed as
195
+ # a multiple of the font height. A line spacing of 1.0 is the minimum to
196
+ # guarantee that lines of text do not collide. The default according to GD
197
+ # is 1.05.
198
+ #
199
+ # - :charmap => Specify a preference for Unicode, Shift_JIS, or Big5
200
+ # character encoding. Use one of the constants
201
+ # Font::TrueType::CHARMAP_UNICODE, Font::TrueType::CHARMAP_SHIFT_JIS, or
202
+ # Font::TrueType::CHARMAP_BIG5.
203
+ #
204
+ # - :hdpi => The horizontal resolution hint for the rendering engine. The
205
+ # default according to GD is 96 dpi.
206
+ #
207
+ # - :vdpi => The vertical resolution hint for the rendering engine. The
208
+ # default according to GD is 96 dpi.
209
+ #
210
+ # - :dpi => A shortcut to specify both :hdpi and :vdpi.
211
+ #
212
+ # - :kerning => A boolean to specify whether kerning tables should be used,
213
+ # if fontconfig is available. The default is *true*.
214
+ #
215
+ def initialize(fontname, ptsize, options = {})
216
+ @fontname, @ptsize = fontname, ptsize.to_f
217
+ @linespacing = options.delete(:linespacing)
218
+ @linespacing = @linespacing.to_f if @linespacing
219
+ @charmap = options.delete(:charmap)
220
+ @hdpi = options.delete(:hdpi)
221
+ @vdpi = options.delete(:vdpi)
222
+ if dpi = options.delete(:dpi)
223
+ @hdpi ||= dpi
224
+ @vdpi ||= dpi
225
+ end
226
+ @kerning = options.delete(:kerning)
227
+ @kerning = true if @kerning.nil?
228
+
229
+ self.class.register(self)
230
+
231
+ # Get the font path (and verify existence of file)
232
+
233
+ strex = strex(false, true)
234
+ args = [ nil, nil, 0, @fontname, @ptsize, 0.0, 0, 0, '', strex ]
235
+ r = GD2FFI.send(:gdImageStringFTEx, *args)
236
+
237
+ raise FreeTypeError.new(r.read_string) unless r.null?
238
+ @fontpath = strex[:fontpath].read_string
239
+ ensure
240
+ GD2FFI.send(:gdFree, strex[:fontpath])
241
+ end
242
+
243
+ def inspect #:nodoc:
244
+ result = "#<#{self.class} #{@fontpath.inspect}, #{@ptsize}"
245
+ result += ", :linespacing => #{@linespacing}" if @linespacing
246
+ result += ", :charmap => #{@charmap}" if @charmap
247
+ result += ", :hdpi => #{@hdpi}" if @hdpi
248
+ result += ", :vdpi => #{@vdpi}" if @vdpi
249
+ result += ", :kerning => #{@kerning}" unless @kerning
250
+ result += '>'
251
+ end
252
+
253
+ def draw(image_ptr, x, y, angle, string, fg) #:nodoc:
254
+ brect = FFI::MemoryPointer.new(:int, 8)
255
+ strex = strex(true)
256
+ args = [ image_ptr, brect, fg, @fontname, @ptsize, angle.to_f, x, y, string.gsub('&', '&amp;'), strex ]
257
+
258
+ r = GD2FFI.send(:gdImageStringFTEx, *args)
259
+ raise FreeTypeError.new(r.read_string) unless r.null?
260
+ brect = brect.read_array_of_int(8)
261
+
262
+ if !strex[:xshow].null? && (xshow = strex[:xshow])
263
+ begin
264
+ xshow = xshow.read_string.split(' ').map { |e| e.to_f }
265
+ ensure
266
+ GD2FFI.send(:gdFree, strex[:xshow])
267
+ end
268
+ else
269
+ xshow = []
270
+ end
271
+
272
+ sum = 0.0
273
+ position = Array.new(xshow.length + 1)
274
+ xshow.each_with_index do |advance, i|
275
+ position[i] = sum
276
+ sum += advance
277
+ end
278
+ position[-1] = sum
279
+
280
+ { :lower_left => [brect[0], brect[1]],
281
+ :lower_right => [brect[2], brect[3]],
282
+ :upper_right => [brect[4], brect[5]],
283
+ :upper_left => [brect[6], brect[7]],
284
+ :position => position
285
+ }
286
+ end
287
+
288
+ def draw_circle(
289
+ image_ptr, cx, cy, radius, text_radius, fill_portion,
290
+ top, bottom, fgcolor
291
+ ) #:nodoc:
292
+ r = GD2FFI.send(
293
+ :gdImageStringFTCircle, image_ptr, cx, cy,
294
+ radius.to_f, text_radius.to_f, fill_portion.to_f, @fontname, @ptsize,
295
+ top || '', bottom || '', fgcolor
296
+ )
297
+ raise FreeTypeError.new(r.read_string) unless r.null?
298
+ nil
299
+ end
300
+
301
+ # Return a hash describing the rectangle that would enclose the given
302
+ # string rendered in this font at the given angle. The returned hash
303
+ # contains the following keys:
304
+ #
305
+ # - :lower_left => The [x, y] coordinates of the lower left corner.
306
+ # - :lower_right => The [x, y] coordinates of the lower right corner.
307
+ # - :upper_right => The [x, y] coordinates of the upper right corner.
308
+ # - :upper_left => The [x, y] coordinates of the upper left corner.
309
+ # - :position => An array of floating point character position offsets for
310
+ # each character of the +string+, beginning with 0.0. The array also
311
+ # includes a final position indicating where the last character ends.
312
+ #
313
+ # The _upper_, _lower_, _left_, and _right_ references are relative to the
314
+ # text of the +string+, regardless of the +angle+.
315
+ def bounding_rectangle(string, angle = 0.0)
316
+ data = draw(nil, 0, 0, angle, string, 0)
317
+
318
+ if string.length == 1
319
+ # gd annoyingly fails to provide xshow data for strings of length 1
320
+ position = draw(nil, 0, 0, angle, string + ' ', 0)[:position]
321
+ data[:position] = position[0...-1]
322
+ end
323
+
324
+ data
325
+ end
326
+
327
+ private
328
+
329
+ def strex(xshow = false, returnfontpathname = false)
330
+ flags = 0
331
+ flags |= FTEX_LINESPACE if @linespacing
332
+ flags |= FTEX_CHARMAP if @charmap
333
+ flags |= FTEX_RESOLUTION if @hdpi || @vdpi
334
+ flags |= FTEX_DISABLE_KERNING unless @kerning
335
+ flags |= FTEX_XSHOW if xshow
336
+ flags |= FTEX_RETURNFONTPATHNAME if returnfontpathname
337
+
338
+ strex = FFIStruct::FTStringExtraPtr.new
339
+ strex[:flags] = flags
340
+ strex[:linespacing] = @linespacing || 0.0
341
+ strex[:charmap] = @charmap ? @charmap : 0
342
+ strex[:hdpi] = @hdpi || @vdpi || 0
343
+ strex[:vdpi] = @vdpi || @hdpi || 0
344
+ strex
345
+ end
346
+ end
347
+ end