rgd2-ffij 0.0.3

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 +215 -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 +343 -0
  12. data/lib/gd2/image.rb +795 -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 +123 -0
@@ -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.to_i, other.rgba.to_i)
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
@@ -0,0 +1,343 @@
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.to_i, y.to_i, string, fg.to_i)
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.exclusive do
118
+ count = @@fontcount
119
+ @@fontcount += 1
120
+
121
+ if count.zero?
122
+ raise FreeTypeError, 'FreeType library failed to initialize' unless
123
+ GD2FFI.send(:gdFontCacheSetup).zero?
124
+ end
125
+
126
+ ObjectSpace.define_finalizer(font, font_finalizer)
127
+ end
128
+ end
129
+
130
+ def self.font_finalizer
131
+ proc { unregister }
132
+ end
133
+
134
+ def self.unregister
135
+ Thread.exclusive do
136
+ @@fontcount -= 1
137
+ GD2FFI.send(:gdFontCacheShutdown) if @@fontcount.zero?
138
+ end
139
+ end
140
+
141
+ private_class_method :font_finalizer, :unregister
142
+
143
+ def self.fontconfig #:nodoc:
144
+ @@fontconfig
145
+ end
146
+
147
+ # Return a boolean indicating whether fontconfig support has been enabled.
148
+ # The default is *false*.
149
+ def self.fontconfig?
150
+ fontconfig
151
+ end
152
+
153
+ # Set whether fontconfig support should be enabled. To use this, the GD
154
+ # library must have been built with fontconfig support. Raises an error if
155
+ # fontconfig support is unavailable.
156
+ def self.fontconfig=(want)
157
+ avail = !GD2FFI.send(:gdFTUseFontConfig, want ? 1 : 0).zero?
158
+ raise FontconfigError, 'Fontconfig not available' if want && !avail
159
+ @@fontconfig = want
160
+ end
161
+
162
+ class << self
163
+ alias [] new
164
+ end
165
+
166
+ # The effective path to this TrueType font
167
+ attr_reader :fontpath
168
+
169
+ # The chosen linespacing
170
+ attr_reader :linespacing
171
+
172
+ # The chosen charmap
173
+ attr_reader :charmap
174
+
175
+ # The chosen horizontal resolution hint
176
+ attr_reader :hdpi
177
+
178
+ # The chosen vertical resolution hint
179
+ attr_reader :vdpi
180
+
181
+ # Whether kerning is desired
182
+ attr_reader :kerning
183
+
184
+ # Instantiate a TrueType font given by +fontname+ (either a pathname or a
185
+ # fontconfig pattern if fontconfig is enabled) and +ptsize+ (a point size
186
+ # given as a floating point number).
187
+ #
188
+ # The possible +options+ are:
189
+ #
190
+ # - :linespacing => The desired line spacing for multiline text, expressed as
191
+ # a multiple of the font height. A line spacing of 1.0 is the minimum to
192
+ # guarantee that lines of text do not collide. The default according to GD
193
+ # is 1.05.
194
+ #
195
+ # - :charmap => Specify a preference for Unicode, Shift_JIS, or Big5
196
+ # character encoding. Use one of the constants
197
+ # Font::TrueType::CHARMAP_UNICODE, Font::TrueType::CHARMAP_SHIFT_JIS, or
198
+ # Font::TrueType::CHARMAP_BIG5.
199
+ #
200
+ # - :hdpi => The horizontal resolution hint for the rendering engine. The
201
+ # default according to GD is 96 dpi.
202
+ #
203
+ # - :vdpi => The vertical resolution hint for the rendering engine. The
204
+ # default according to GD is 96 dpi.
205
+ #
206
+ # - :dpi => A shortcut to specify both :hdpi and :vdpi.
207
+ #
208
+ # - :kerning => A boolean to specify whether kerning tables should be used,
209
+ # if fontconfig is available. The default is *true*.
210
+ #
211
+ def initialize(fontname, ptsize, options = {})
212
+ @fontname, @ptsize = fontname, ptsize.to_f
213
+ @linespacing = options.delete(:linespacing)
214
+ @linespacing = @linespacing.to_f if @linespacing
215
+ @charmap = options.delete(:charmap)
216
+ @hdpi = options.delete(:hdpi)
217
+ @vdpi = options.delete(:vdpi)
218
+ if dpi = options.delete(:dpi)
219
+ @hdpi ||= dpi
220
+ @vdpi ||= dpi
221
+ end
222
+ @kerning = options.delete(:kerning)
223
+ @kerning = true if @kerning.nil?
224
+
225
+ self.class.register(self)
226
+
227
+ # Get the font path (and verify existence of file)
228
+
229
+ strex = strex(false, true)
230
+ args = [ nil, nil, 0, @fontname, @ptsize, 0.0, 0, 0, '', strex ]
231
+ r = GD2FFI.send(:gdImageStringFTEx, *args)
232
+
233
+ raise FreeTypeError.new(r.read_string) unless r.null?
234
+ @fontpath = strex[:fontpath].read_string
235
+ ensure
236
+ GD2FFI.send(:gdFree, strex[:fontpath])
237
+ end
238
+
239
+ def inspect #:nodoc:
240
+ result = "#<#{self.class} #{@fontpath.inspect}, #{@ptsize}"
241
+ result += ", :linespacing => #{@linespacing}" if @linespacing
242
+ result += ", :charmap => #{@charmap}" if @charmap
243
+ result += ", :hdpi => #{@hdpi}" if @hdpi
244
+ result += ", :vdpi => #{@vdpi}" if @vdpi
245
+ result += ", :kerning => #{@kerning}" unless @kerning
246
+ result += '>'
247
+ end
248
+
249
+ def draw(image_ptr, x, y, angle, string, fg) #:nodoc:
250
+ brect = FFI::MemoryPointer.new(:int, 8)
251
+ strex = strex(true)
252
+ args = [ image_ptr, brect, fg, @fontname, @ptsize, angle.to_f, x.to_i, y.to_i, string.gsub('&', '&amp;'), strex ]
253
+
254
+ r = GD2FFI.send(:gdImageStringFTEx, *args)
255
+ raise FreeTypeError.new(r.read_string) unless r.null?
256
+ brect = brect.read_array_of_int(8)
257
+
258
+ if !strex[:xshow].null? && (xshow = strex[:xshow])
259
+ begin
260
+ xshow = xshow.read_string.split(' ').map { |e| e.to_f }
261
+ ensure
262
+ GD2FFI.send(:gdFree, strex[:xshow])
263
+ end
264
+ else
265
+ xshow = []
266
+ end
267
+
268
+ sum = 0.0
269
+ position = Array.new(xshow.length + 1)
270
+ xshow.each_with_index do |advance, i|
271
+ position[i] = sum
272
+ sum += advance
273
+ end
274
+ position[-1] = sum
275
+
276
+ { :lower_left => [brect[0], brect[1]],
277
+ :lower_right => [brect[2], brect[3]],
278
+ :upper_right => [brect[4], brect[5]],
279
+ :upper_left => [brect[6], brect[7]],
280
+ :position => position
281
+ }
282
+ end
283
+
284
+ def draw_circle(
285
+ image_ptr, cx, cy, radius, text_radius, fill_portion,
286
+ top, bottom, fgcolor
287
+ ) #:nodoc:
288
+ r = GD2FFI.send(
289
+ :gdImageStringFTCircle, image_ptr, cx.to_i, cy.to_i,
290
+ radius.to_f, text_radius.to_f, fill_portion.to_f, @fontname, @ptsize,
291
+ top || '', bottom || '', fgcolor.to_i
292
+ )
293
+ raise FreeTypeError.new(r.read_string) unless r.null?
294
+ nil
295
+ end
296
+
297
+ # Return a hash describing the rectangle that would enclose the given
298
+ # string rendered in this font at the given angle. The returned hash
299
+ # contains the following keys:
300
+ #
301
+ # - :lower_left => The [x, y] coordinates of the lower left corner.
302
+ # - :lower_right => The [x, y] coordinates of the lower right corner.
303
+ # - :upper_right => The [x, y] coordinates of the upper right corner.
304
+ # - :upper_left => The [x, y] coordinates of the upper left corner.
305
+ # - :position => An array of floating point character position offsets for
306
+ # each character of the +string+, beginning with 0.0. The array also
307
+ # includes a final position indicating where the last character ends.
308
+ #
309
+ # The _upper_, _lower_, _left_, and _right_ references are relative to the
310
+ # text of the +string+, regardless of the +angle+.
311
+ def bounding_rectangle(string, angle = 0.0)
312
+ data = draw(nil, 0, 0, angle, string, 0)
313
+
314
+ if string.length == 1
315
+ # gd annoyingly fails to provide xshow data for strings of length 1
316
+ position = draw(nil, 0, 0, angle, string + ' ', 0)[:position]
317
+ data[:position] = position[0...-1]
318
+ end
319
+
320
+ data
321
+ end
322
+
323
+ private
324
+
325
+ def strex(xshow = false, returnfontpathname = false)
326
+ flags = 0
327
+ flags |= FTEX_LINESPACE if @linespacing
328
+ flags |= FTEX_CHARMAP if @charmap
329
+ flags |= FTEX_RESOLUTION if @hdpi || @vdpi
330
+ flags |= FTEX_DISABLE_KERNING unless @kerning
331
+ flags |= FTEX_XSHOW if xshow
332
+ flags |= FTEX_RETURNFONTPATHNAME if returnfontpathname
333
+
334
+ strex = FFIStruct::FTStringExtraPtr.new
335
+ strex[:flags] = flags
336
+ strex[:linespacing] = @linespacing || 0.0
337
+ strex[:charmap] = @charmap ? @charmap : 0
338
+ strex[:hdpi] = @hdpi || @vdpi || 0
339
+ strex[:vdpi] = @vdpi || @hdpi || 0
340
+ strex
341
+ end
342
+ end
343
+ end