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.
- data/.gitignore +5 -0
- data/COPYING +340 -0
- data/COPYRIGHT +17 -0
- data/README +34 -0
- data/Rakefile +32 -0
- data/gd2-ffij.gemspec +96 -0
- data/lib/gd2-ffij.rb +209 -0
- data/lib/gd2/canvas.rb +422 -0
- data/lib/gd2/color.rb +240 -0
- data/lib/gd2/ffi_struct.rb +76 -0
- data/lib/gd2/font.rb +347 -0
- data/lib/gd2/image.rb +785 -0
- data/lib/gd2/palette.rb +253 -0
- data/test/canvas_test.rb +186 -0
- data/test/image_test.rb +149 -0
- data/test/images/test.bmp +0 -0
- data/test/images/test.gd +0 -0
- data/test/images/test.gd2 +0 -0
- data/test/images/test.gif +0 -0
- data/test/images/test.jpg +0 -0
- data/test/images/test.png +0 -0
- data/test/images/test.wbmp +0 -0
- data/test/images/test.xbm +686 -0
- data/test/images/test.xcf +0 -0
- data/test/images/test.xpm +261 -0
- data/test/images/test_arc.gd2 +0 -0
- data/test/images/test_canvas_filled_polygon.gd2 +0 -0
- data/test/images/test_canvas_filled_rectangle.gd2 +0 -0
- data/test/images/test_canvas_line.gd2 +0 -0
- data/test/images/test_canvas_move_to_and_line_to.gd2 +0 -0
- data/test/images/test_canvas_polygon.gd2 +0 -0
- data/test/images/test_canvas_rectangle.gd2 +0 -0
- data/test/images/test_circle.gd2 +0 -0
- data/test/images/test_color.gd2 +0 -0
- data/test/images/test_color.png +0 -0
- data/test/images/test_color.xcf +0 -0
- data/test/images/test_color_indexed.gd2 +0 -0
- data/test/images/test_color_sharpened.gd2 +0 -0
- data/test/images/test_cropped.gd2 +0 -0
- data/test/images/test_ellipse.gd2 +0 -0
- data/test/images/test_fill.gd2 +0 -0
- data/test/images/test_fill_to.gd2 +0 -0
- data/test/images/test_filled_circle.gd2 +0 -0
- data/test/images/test_filled_ellipse.gd2 +0 -0
- data/test/images/test_filled_wedge.gd2 +0 -0
- data/test/images/test_polar_transform.gd2 +0 -0
- data/test/images/test_resampled.gd2 +0 -0
- data/test/images/test_resized.gd2 +0 -0
- data/test/images/test_rotated_180.gd2 +0 -0
- data/test/images/test_text.gd2 +0 -0
- data/test/images/test_text_circle.gd2 +0 -0
- data/test/images/test_wedge.gd2 +0 -0
- data/test/test_helper.rb +13 -0
- data/vendor/fonts/ttf/DejaVuSans.ttf +0 -0
- data/vendor/fonts/ttf/LICENSE +99 -0
- 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('&', '&'), 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
|