gd2-ffij 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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