gd2-ffij 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|