harfbuzz-ruby 1.0.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +258 -0
- data/Rakefile +8 -0
- data/benchmark/shaping_bench.rb +77 -0
- data/examples/basic_shaping.rb +67 -0
- data/examples/glyph_outlines.rb +79 -0
- data/examples/opentype_features.rb +91 -0
- data/examples/render_svg.rb +112 -0
- data/examples/render_waterfall.rb +177 -0
- data/examples/variable_fonts.rb +73 -0
- data/lib/harfbuzz/aat/layout.rb +78 -0
- data/lib/harfbuzz/blob.rb +136 -0
- data/lib/harfbuzz/buffer.rb +497 -0
- data/lib/harfbuzz/c/aat/layout.rb +15 -0
- data/lib/harfbuzz/c/base.rb +114 -0
- data/lib/harfbuzz/c/blob.rb +23 -0
- data/lib/harfbuzz/c/buffer.rb +127 -0
- data/lib/harfbuzz/c/common.rb +39 -0
- data/lib/harfbuzz/c/draw.rb +22 -0
- data/lib/harfbuzz/c/enums.rb +146 -0
- data/lib/harfbuzz/c/face.rb +37 -0
- data/lib/harfbuzz/c/font.rb +88 -0
- data/lib/harfbuzz/c/font_funcs.rb +58 -0
- data/lib/harfbuzz/c/map.rb +28 -0
- data/lib/harfbuzz/c/ot/color.rb +32 -0
- data/lib/harfbuzz/c/ot/font.rb +7 -0
- data/lib/harfbuzz/c/ot/layout.rb +83 -0
- data/lib/harfbuzz/c/ot/math.rb +23 -0
- data/lib/harfbuzz/c/ot/meta.rb +10 -0
- data/lib/harfbuzz/c/ot/metrics.rb +16 -0
- data/lib/harfbuzz/c/ot/name.rb +13 -0
- data/lib/harfbuzz/c/ot/shape.rb +10 -0
- data/lib/harfbuzz/c/ot/var.rb +22 -0
- data/lib/harfbuzz/c/paint.rb +38 -0
- data/lib/harfbuzz/c/set.rb +42 -0
- data/lib/harfbuzz/c/shape.rb +11 -0
- data/lib/harfbuzz/c/shape_plan.rb +24 -0
- data/lib/harfbuzz/c/structs.rb +120 -0
- data/lib/harfbuzz/c/subset.rb +49 -0
- data/lib/harfbuzz/c/unicode.rb +40 -0
- data/lib/harfbuzz/c/version.rb +25 -0
- data/lib/harfbuzz/draw_funcs.rb +112 -0
- data/lib/harfbuzz/error.rb +27 -0
- data/lib/harfbuzz/face.rb +186 -0
- data/lib/harfbuzz/feature.rb +76 -0
- data/lib/harfbuzz/flags.rb +85 -0
- data/lib/harfbuzz/font.rb +404 -0
- data/lib/harfbuzz/font_funcs.rb +286 -0
- data/lib/harfbuzz/glyph_info.rb +35 -0
- data/lib/harfbuzz/glyph_position.rb +41 -0
- data/lib/harfbuzz/library.rb +98 -0
- data/lib/harfbuzz/map.rb +157 -0
- data/lib/harfbuzz/ot/color.rb +125 -0
- data/lib/harfbuzz/ot/font.rb +16 -0
- data/lib/harfbuzz/ot/layout.rb +583 -0
- data/lib/harfbuzz/ot/math.rb +111 -0
- data/lib/harfbuzz/ot/meta.rb +34 -0
- data/lib/harfbuzz/ot/metrics.rb +54 -0
- data/lib/harfbuzz/ot/name.rb +81 -0
- data/lib/harfbuzz/ot/shape.rb +34 -0
- data/lib/harfbuzz/ot/var.rb +116 -0
- data/lib/harfbuzz/paint_funcs.rb +134 -0
- data/lib/harfbuzz/set.rb +272 -0
- data/lib/harfbuzz/shape_plan.rb +115 -0
- data/lib/harfbuzz/shaping_result.rb +94 -0
- data/lib/harfbuzz/subset.rb +130 -0
- data/lib/harfbuzz/unicode_funcs.rb +201 -0
- data/lib/harfbuzz/variation.rb +49 -0
- data/lib/harfbuzz/version.rb +5 -0
- data/lib/harfbuzz-ffi.rb +4 -0
- data/lib/harfbuzz.rb +313 -0
- data/sig/harfbuzz.rbs +594 -0
- metadata +132 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HarfBuzz
|
|
4
|
+
# Wraps hb_unicode_funcs_t — Unicode property callbacks
|
|
5
|
+
class UnicodeFuncs
|
|
6
|
+
attr_reader :ptr
|
|
7
|
+
|
|
8
|
+
# Creates a new UnicodeFuncs, optionally inheriting from a parent
|
|
9
|
+
# @param parent [UnicodeFuncs, nil] Parent funcs (nil = use default)
|
|
10
|
+
def initialize(parent = nil)
|
|
11
|
+
parent_ptr = parent ? parent.ptr : C.hb_unicode_funcs_get_default
|
|
12
|
+
@ptr = C.hb_unicode_funcs_create(parent_ptr)
|
|
13
|
+
raise AllocationError, "Failed to create unicode_funcs" if @ptr.null?
|
|
14
|
+
|
|
15
|
+
HarfBuzz::UnicodeFuncs.define_finalizer(self, @ptr)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Returns the default Unicode functions
|
|
19
|
+
# @return [UnicodeFuncs]
|
|
20
|
+
def self.default
|
|
21
|
+
wrap_borrowed(C.hb_unicode_funcs_get_default)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns the empty Unicode functions
|
|
25
|
+
# @return [UnicodeFuncs]
|
|
26
|
+
def self.empty
|
|
27
|
+
wrap_borrowed(C.hb_unicode_funcs_get_empty)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the parent functions
|
|
31
|
+
# @return [UnicodeFuncs]
|
|
32
|
+
def parent
|
|
33
|
+
self.class.wrap_borrowed(C.hb_unicode_funcs_get_parent(@ptr))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Boolean] true if immutable
|
|
37
|
+
def immutable?
|
|
38
|
+
C.from_hb_bool(C.hb_unicode_funcs_is_immutable(@ptr))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Makes immutable
|
|
42
|
+
# @return [self]
|
|
43
|
+
def make_immutable!
|
|
44
|
+
C.hb_unicode_funcs_make_immutable(@ptr)
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Sets a custom general_category function
|
|
49
|
+
# @yield [codepoint] Returns general category symbol
|
|
50
|
+
def on_general_category(&block)
|
|
51
|
+
@general_category_callback = block
|
|
52
|
+
cb = FFI::Function.new(:int, [:pointer, :uint32, :pointer]) do |_ufuncs, cp, _user_data|
|
|
53
|
+
result = block.call(cp)
|
|
54
|
+
C::UnicodeGeneralCategory[result] || result
|
|
55
|
+
end
|
|
56
|
+
@general_category_ffi = cb
|
|
57
|
+
C.hb_unicode_funcs_set_general_category_func(@ptr, cb, nil, nil)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Sets a custom combining_class function
|
|
61
|
+
# @yield [codepoint] Returns combining class integer
|
|
62
|
+
def on_combining_class(&block)
|
|
63
|
+
@combining_class_callback = block
|
|
64
|
+
cb = FFI::Function.new(:uint, [:pointer, :uint32, :pointer]) do |_ufuncs, cp, _user_data|
|
|
65
|
+
block.call(cp)
|
|
66
|
+
end
|
|
67
|
+
@combining_class_ffi = cb
|
|
68
|
+
C.hb_unicode_funcs_set_combining_class_func(@ptr, cb, nil, nil)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Sets a custom mirroring function
|
|
72
|
+
# @yield [codepoint] Returns mirrored codepoint
|
|
73
|
+
def on_mirroring(&block)
|
|
74
|
+
@mirroring_callback = block
|
|
75
|
+
cb = FFI::Function.new(:uint32, [:pointer, :uint32, :pointer]) do |_ufuncs, cp, _user_data|
|
|
76
|
+
block.call(cp)
|
|
77
|
+
end
|
|
78
|
+
@mirroring_ffi = cb
|
|
79
|
+
C.hb_unicode_funcs_set_mirroring_func(@ptr, cb, nil, nil)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Sets a custom script function
|
|
83
|
+
# @yield [codepoint] Returns script value
|
|
84
|
+
def on_script(&block)
|
|
85
|
+
@script_callback = block
|
|
86
|
+
cb = FFI::Function.new(:uint32, [:pointer, :uint32, :pointer]) do |_ufuncs, cp, _user_data|
|
|
87
|
+
block.call(cp)
|
|
88
|
+
end
|
|
89
|
+
@script_ffi = cb
|
|
90
|
+
C.hb_unicode_funcs_set_script_func(@ptr, cb, nil, nil)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Sets a custom compose function
|
|
94
|
+
# @yield [a, b] Returns composed codepoint or nil
|
|
95
|
+
def on_compose(&block)
|
|
96
|
+
@compose_callback = block
|
|
97
|
+
cb = FFI::Function.new(:int, [:pointer, :uint32, :uint32, :pointer, :pointer]) do
|
|
98
|
+
|_ufuncs, a, b, ab_ptr, _user_data|
|
|
99
|
+
result = block.call(a, b)
|
|
100
|
+
if result
|
|
101
|
+
ab_ptr.write_uint32(result)
|
|
102
|
+
1
|
|
103
|
+
else
|
|
104
|
+
0
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
@compose_ffi = cb
|
|
108
|
+
C.hb_unicode_funcs_set_compose_func(@ptr, cb, nil, nil)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Sets a custom decompose function
|
|
112
|
+
# @yield [ab] Returns [a, b] array or nil
|
|
113
|
+
def on_decompose(&block)
|
|
114
|
+
@decompose_callback = block
|
|
115
|
+
cb = FFI::Function.new(:int, [:pointer, :uint32, :pointer, :pointer, :pointer]) do
|
|
116
|
+
|_ufuncs, ab, a_ptr, b_ptr, _user_data|
|
|
117
|
+
result = block.call(ab)
|
|
118
|
+
if result
|
|
119
|
+
a_ptr.write_uint32(result[0])
|
|
120
|
+
b_ptr.write_uint32(result[1])
|
|
121
|
+
1
|
|
122
|
+
else
|
|
123
|
+
0
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
@decompose_ffi = cb
|
|
127
|
+
C.hb_unicode_funcs_set_decompose_func(@ptr, cb, nil, nil)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Queries the general category of a codepoint
|
|
131
|
+
# @param cp [Integer] Unicode codepoint
|
|
132
|
+
# @return [Symbol] General category
|
|
133
|
+
def general_category(cp)
|
|
134
|
+
C.hb_unicode_general_category(@ptr, cp)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Queries the combining class of a codepoint
|
|
138
|
+
# @param cp [Integer] Unicode codepoint
|
|
139
|
+
# @return [Integer] Combining class
|
|
140
|
+
def combining_class(cp)
|
|
141
|
+
C.hb_unicode_combining_class(@ptr, cp)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Queries the mirror codepoint
|
|
145
|
+
# @param cp [Integer] Unicode codepoint
|
|
146
|
+
# @return [Integer] Mirrored codepoint (or same if no mirror)
|
|
147
|
+
def mirroring(cp)
|
|
148
|
+
C.hb_unicode_mirroring(@ptr, cp)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Queries the script of a codepoint
|
|
152
|
+
# @param cp [Integer] Unicode codepoint
|
|
153
|
+
# @return [Integer] Script value
|
|
154
|
+
def script(cp)
|
|
155
|
+
C.hb_unicode_script(@ptr, cp)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Composes two codepoints into one
|
|
159
|
+
# @param a [Integer] First codepoint
|
|
160
|
+
# @param b [Integer] Second codepoint
|
|
161
|
+
# @return [Integer, nil] Composed codepoint or nil
|
|
162
|
+
def compose(a, b)
|
|
163
|
+
ab_ptr = FFI::MemoryPointer.new(:uint32)
|
|
164
|
+
ok = C.hb_unicode_compose(@ptr, a, b, ab_ptr)
|
|
165
|
+
ok.zero? ? nil : ab_ptr.read_uint32
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Decomposes a codepoint into two
|
|
169
|
+
# @param cp [Integer] Codepoint to decompose
|
|
170
|
+
# @return [Array<Integer>, nil] [a, b] or nil
|
|
171
|
+
def decompose(cp)
|
|
172
|
+
a_ptr = FFI::MemoryPointer.new(:uint32)
|
|
173
|
+
b_ptr = FFI::MemoryPointer.new(:uint32)
|
|
174
|
+
ok = C.hb_unicode_decompose(@ptr, cp, a_ptr, b_ptr)
|
|
175
|
+
ok.zero? ? nil : [a_ptr.read_uint32, b_ptr.read_uint32]
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def inspect
|
|
179
|
+
"#<HarfBuzz::UnicodeFuncs immutable=#{immutable?}>"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def self.wrap_owned(ptr)
|
|
183
|
+
obj = allocate
|
|
184
|
+
obj.instance_variable_set(:@ptr, ptr)
|
|
185
|
+
define_finalizer(obj, ptr)
|
|
186
|
+
obj
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def self.wrap_borrowed(ptr)
|
|
190
|
+
obj = allocate
|
|
191
|
+
obj.instance_variable_set(:@ptr, ptr)
|
|
192
|
+
obj.instance_variable_set(:@borrowed, true)
|
|
193
|
+
obj
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def self.define_finalizer(obj, ptr)
|
|
197
|
+
destroy = C.method(:hb_unicode_funcs_destroy)
|
|
198
|
+
ObjectSpace.define_finalizer(obj, proc { destroy.call(ptr) })
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HarfBuzz
|
|
4
|
+
# Represents an OpenType variation axis value (e.g., "wght=700")
|
|
5
|
+
class Variation
|
|
6
|
+
def initialize(struct)
|
|
7
|
+
@struct = struct
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# @return [Integer] Variation axis tag as uint32
|
|
11
|
+
def tag
|
|
12
|
+
@struct[:tag]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @return [Float] Variation axis value
|
|
16
|
+
def value
|
|
17
|
+
@struct[:value]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Parses a variation string such as "wght=700" or "ital=1"
|
|
21
|
+
# @param str [String] Variation string
|
|
22
|
+
# @return [Variation] Parsed variation
|
|
23
|
+
# @raise [VariationParseError] If the string cannot be parsed
|
|
24
|
+
def self.from_string(str)
|
|
25
|
+
struct = C::HbVariationT.new
|
|
26
|
+
result = C.hb_variation_from_string(str, str.bytesize, struct)
|
|
27
|
+
raise VariationParseError, "Invalid variation string: #{str.inspect}" if result.zero?
|
|
28
|
+
|
|
29
|
+
new(struct)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns the FFI struct for passing to C functions
|
|
33
|
+
# @return [C::HbVariationT]
|
|
34
|
+
def to_struct
|
|
35
|
+
@struct
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [String] Variation string representation
|
|
39
|
+
def to_s
|
|
40
|
+
buf = FFI::MemoryPointer.new(:char, 64)
|
|
41
|
+
C.hb_variation_to_string(@struct, buf, 64)
|
|
42
|
+
buf.read_string
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def inspect
|
|
46
|
+
"#<HarfBuzz::Variation #{to_s}>"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/harfbuzz-ffi.rb
ADDED
data/lib/harfbuzz.rb
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "harfbuzz/version"
|
|
4
|
+
require_relative "harfbuzz/error"
|
|
5
|
+
require_relative "harfbuzz/library"
|
|
6
|
+
|
|
7
|
+
# Load FFI low-level layer
|
|
8
|
+
require_relative "harfbuzz/c/base"
|
|
9
|
+
require_relative "harfbuzz/c/enums"
|
|
10
|
+
require_relative "harfbuzz/c/structs"
|
|
11
|
+
require_relative "harfbuzz/c/version"
|
|
12
|
+
require_relative "harfbuzz/c/common"
|
|
13
|
+
require_relative "harfbuzz/c/blob"
|
|
14
|
+
require_relative "harfbuzz/c/buffer"
|
|
15
|
+
require_relative "harfbuzz/c/face"
|
|
16
|
+
require_relative "harfbuzz/c/font"
|
|
17
|
+
require_relative "harfbuzz/c/font_funcs"
|
|
18
|
+
require_relative "harfbuzz/c/shape"
|
|
19
|
+
require_relative "harfbuzz/c/map"
|
|
20
|
+
require_relative "harfbuzz/c/set"
|
|
21
|
+
require_relative "harfbuzz/c/shape_plan"
|
|
22
|
+
require_relative "harfbuzz/c/unicode"
|
|
23
|
+
require_relative "harfbuzz/c/draw"
|
|
24
|
+
require_relative "harfbuzz/c/paint"
|
|
25
|
+
require_relative "harfbuzz/c/ot/layout"
|
|
26
|
+
require_relative "harfbuzz/c/ot/var"
|
|
27
|
+
require_relative "harfbuzz/c/ot/color"
|
|
28
|
+
require_relative "harfbuzz/c/ot/math"
|
|
29
|
+
require_relative "harfbuzz/c/ot/meta"
|
|
30
|
+
require_relative "harfbuzz/c/ot/metrics"
|
|
31
|
+
require_relative "harfbuzz/c/ot/name"
|
|
32
|
+
require_relative "harfbuzz/c/ot/shape"
|
|
33
|
+
require_relative "harfbuzz/c/ot/font"
|
|
34
|
+
require_relative "harfbuzz/c/aat/layout"
|
|
35
|
+
|
|
36
|
+
# Load high-level Ruby layer
|
|
37
|
+
require_relative "harfbuzz/glyph_info"
|
|
38
|
+
require_relative "harfbuzz/glyph_position"
|
|
39
|
+
require_relative "harfbuzz/feature"
|
|
40
|
+
require_relative "harfbuzz/variation"
|
|
41
|
+
require_relative "harfbuzz/blob"
|
|
42
|
+
require_relative "harfbuzz/buffer"
|
|
43
|
+
require_relative "harfbuzz/face"
|
|
44
|
+
require_relative "harfbuzz/font"
|
|
45
|
+
require_relative "harfbuzz/font_funcs"
|
|
46
|
+
require_relative "harfbuzz/flags"
|
|
47
|
+
require_relative "harfbuzz/map"
|
|
48
|
+
require_relative "harfbuzz/set"
|
|
49
|
+
require_relative "harfbuzz/shape_plan"
|
|
50
|
+
require_relative "harfbuzz/unicode_funcs"
|
|
51
|
+
require_relative "harfbuzz/draw_funcs"
|
|
52
|
+
require_relative "harfbuzz/paint_funcs"
|
|
53
|
+
require_relative "harfbuzz/shaping_result"
|
|
54
|
+
|
|
55
|
+
# OT namespace
|
|
56
|
+
module HarfBuzz
|
|
57
|
+
module OT; end
|
|
58
|
+
end
|
|
59
|
+
require_relative "harfbuzz/ot/layout"
|
|
60
|
+
require_relative "harfbuzz/ot/var"
|
|
61
|
+
require_relative "harfbuzz/ot/color"
|
|
62
|
+
require_relative "harfbuzz/ot/math"
|
|
63
|
+
require_relative "harfbuzz/ot/meta"
|
|
64
|
+
require_relative "harfbuzz/ot/metrics"
|
|
65
|
+
require_relative "harfbuzz/ot/name"
|
|
66
|
+
require_relative "harfbuzz/ot/shape"
|
|
67
|
+
require_relative "harfbuzz/ot/font"
|
|
68
|
+
|
|
69
|
+
# AAT namespace
|
|
70
|
+
module HarfBuzz
|
|
71
|
+
module AAT; end
|
|
72
|
+
end
|
|
73
|
+
require_relative "harfbuzz/aat/layout"
|
|
74
|
+
|
|
75
|
+
# Subset (loads libharfbuzz-subset if available)
|
|
76
|
+
require_relative "harfbuzz/c/subset"
|
|
77
|
+
require_relative "harfbuzz/subset"
|
|
78
|
+
|
|
79
|
+
# HarfBuzz Ruby FFI Bindings
|
|
80
|
+
#
|
|
81
|
+
# This module provides complete Ruby bindings for the HarfBuzz text shaping engine.
|
|
82
|
+
# It uses FFI to provide both low-level C API access (HarfBuzz::C) and high-level
|
|
83
|
+
# Ruby-friendly interfaces.
|
|
84
|
+
#
|
|
85
|
+
# @example Basic shaping
|
|
86
|
+
# blob = HarfBuzz::Blob.from_file!("font.ttf")
|
|
87
|
+
# face = HarfBuzz::Face.new(blob, 0)
|
|
88
|
+
# font = HarfBuzz::Font.new(face)
|
|
89
|
+
# buffer = HarfBuzz::Buffer.new
|
|
90
|
+
# buffer.add_utf8("Hello")
|
|
91
|
+
# buffer.guess_segment_properties
|
|
92
|
+
# HarfBuzz.shape(font, buffer)
|
|
93
|
+
module HarfBuzz
|
|
94
|
+
# Returns the HarfBuzz library version as an array [major, minor, micro]
|
|
95
|
+
# @return [Array<Integer>] Version array
|
|
96
|
+
def self.version
|
|
97
|
+
major_ptr = FFI::MemoryPointer.new(:uint)
|
|
98
|
+
minor_ptr = FFI::MemoryPointer.new(:uint)
|
|
99
|
+
micro_ptr = FFI::MemoryPointer.new(:uint)
|
|
100
|
+
|
|
101
|
+
C.hb_version(major_ptr, minor_ptr, micro_ptr)
|
|
102
|
+
|
|
103
|
+
[major_ptr.read_uint, minor_ptr.read_uint, micro_ptr.read_uint]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Returns the HarfBuzz library version as a string
|
|
107
|
+
# @return [String] Version string (e.g., "8.3.0")
|
|
108
|
+
def self.version_string
|
|
109
|
+
C.hb_version_string
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Checks if the library version is at least the specified version
|
|
113
|
+
# @param major [Integer] Major version
|
|
114
|
+
# @param minor [Integer] Minor version
|
|
115
|
+
# @param micro [Integer] Micro version
|
|
116
|
+
# @return [Boolean] true if version >= specified
|
|
117
|
+
def self.version_atleast?(major, minor, micro)
|
|
118
|
+
C.from_hb_bool(C.hb_version_atleast(major, minor, micro))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
class << self
|
|
122
|
+
alias version_at_least? version_atleast?
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Converts a 4-character string, Symbol, or Integer to an OpenType tag (uint32)
|
|
126
|
+
# @param str [String, Symbol, Integer] Tag as string (e.g., "GSUB"), symbol, or uint32 integer
|
|
127
|
+
# @return [Integer] Tag as uint32
|
|
128
|
+
def self.tag(str)
|
|
129
|
+
case str
|
|
130
|
+
when Integer then str
|
|
131
|
+
when Symbol then tag(str.to_s)
|
|
132
|
+
when String then C.hb_tag_from_string(str, str.bytesize)
|
|
133
|
+
else raise InvalidArgumentError, "Cannot convert #{str.class} to tag"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Converts an OpenType tag (uint32) to a 4-character string
|
|
138
|
+
# @param tag [Integer] Tag as uint32
|
|
139
|
+
# @return [String] 4-character tag string
|
|
140
|
+
def self.tag_to_s(tag)
|
|
141
|
+
buf = FFI::MemoryPointer.new(:char, 4)
|
|
142
|
+
C.hb_tag_to_string(tag, buf)
|
|
143
|
+
buf.read_bytes(4)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Converts a direction string to a direction enum value
|
|
147
|
+
# @param str [String] Direction string (e.g., "ltr")
|
|
148
|
+
# @return [Symbol] Direction symbol
|
|
149
|
+
def self.direction(str)
|
|
150
|
+
C.hb_direction_from_string(str, str.bytesize)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Returns a language opaque pointer from a BCP 47 language tag string
|
|
154
|
+
# @param str [String] BCP 47 language tag (e.g., "en", "ar")
|
|
155
|
+
# @return [FFI::Pointer] Language pointer
|
|
156
|
+
def self.language(str)
|
|
157
|
+
C.hb_language_from_string(str, str.bytesize)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Returns the default language
|
|
161
|
+
# @return [FFI::Pointer] Default language pointer
|
|
162
|
+
def self.default_language
|
|
163
|
+
C.hb_language_get_default
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Returns the BCP 47 string representation of a language pointer
|
|
167
|
+
# @param lang [FFI::Pointer] Language pointer (from HarfBuzz.language)
|
|
168
|
+
# @return [String] BCP 47 language tag string
|
|
169
|
+
def self.language_to_s(lang)
|
|
170
|
+
C.hb_language_to_string(lang)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Checks if two language tags match (BCP 47 prefix comparison)
|
|
174
|
+
# @param lang1 [FFI::Pointer] First language pointer
|
|
175
|
+
# @param lang2 [FFI::Pointer] Second language pointer
|
|
176
|
+
# @return [Boolean] true if languages match
|
|
177
|
+
def self.language_matches?(lang1, lang2)
|
|
178
|
+
C.from_hb_bool(C.hb_language_matches(lang1, lang2))
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Converts a script name string to a script value
|
|
182
|
+
# @param str [String] Script name (e.g., "Arab", "Latn")
|
|
183
|
+
# @return [Integer] Script value
|
|
184
|
+
def self.script(str)
|
|
185
|
+
C.hb_script_from_string(str, str.bytesize)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Converts an ISO 15924 OpenType tag to a script value
|
|
189
|
+
# @param tag [Integer] ISO 15924 tag (e.g., HarfBuzz.tag("Arab"))
|
|
190
|
+
# @return [Integer] Script value
|
|
191
|
+
def self.script_from_tag(tag)
|
|
192
|
+
C.hb_script_from_iso15924_tag(tag)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Extracts the alpha component from an RGBA color value
|
|
196
|
+
# @param color [Integer] RGBA color as uint32
|
|
197
|
+
# @return [Integer] Alpha (0-255)
|
|
198
|
+
def self.color_alpha(color)
|
|
199
|
+
C.hb_color_get_alpha(color)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Extracts the red component from an RGBA color value
|
|
203
|
+
# @param color [Integer] RGBA color as uint32
|
|
204
|
+
# @return [Integer] Red (0-255)
|
|
205
|
+
def self.color_red(color)
|
|
206
|
+
C.hb_color_get_red(color)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Extracts the green component from an RGBA color value
|
|
210
|
+
# @param color [Integer] RGBA color as uint32
|
|
211
|
+
# @return [Integer] Green (0-255)
|
|
212
|
+
def self.color_green(color)
|
|
213
|
+
C.hb_color_get_green(color)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Extracts the blue component from an RGBA color value
|
|
217
|
+
# @param color [Integer] RGBA color as uint32
|
|
218
|
+
# @return [Integer] Blue (0-255)
|
|
219
|
+
def self.color_blue(color)
|
|
220
|
+
C.hb_color_get_blue(color)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Returns the horizontal direction for a script
|
|
224
|
+
# @param script [Integer] Script value
|
|
225
|
+
# @return [Symbol] Direction symbol (:ltr or :rtl)
|
|
226
|
+
def self.script_horizontal_direction(script)
|
|
227
|
+
C.hb_script_get_horizontal_direction(script)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Shapes text in the buffer using the font
|
|
231
|
+
# @param font [Font] Font to use for shaping
|
|
232
|
+
# @param buffer [Buffer] Buffer containing text to shape
|
|
233
|
+
# @param features [Array<Feature>] Optional features to apply
|
|
234
|
+
# @param shapers [Array<String>, nil] Optional list of shapers to try
|
|
235
|
+
def self.shape(font, buffer, features = [], shapers: nil)
|
|
236
|
+
features_ptr = build_features_ptr(features)
|
|
237
|
+
|
|
238
|
+
if shapers
|
|
239
|
+
shapers_ptrs = shapers.map { |s| FFI::MemoryPointer.from_string(s) }
|
|
240
|
+
shapers_ptrs << FFI::Pointer::NULL
|
|
241
|
+
shapers_ptr = FFI::MemoryPointer.new(:pointer, shapers_ptrs.size)
|
|
242
|
+
shapers_ptrs.each_with_index { |p, i| shapers_ptr[i].put_pointer(0, p) }
|
|
243
|
+
C.hb_shape_full(font.ptr, buffer.ptr, features_ptr, features.size, shapers_ptr)
|
|
244
|
+
else
|
|
245
|
+
C.hb_shape(font.ptr, buffer.ptr, features_ptr, features.size)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Returns the list of available shapers
|
|
250
|
+
# @return [Array<String>] List of shaper names
|
|
251
|
+
def self.shapers
|
|
252
|
+
ptr = C.hb_shape_list_shapers
|
|
253
|
+
result = []
|
|
254
|
+
i = 0
|
|
255
|
+
loop do
|
|
256
|
+
p = ptr.get_pointer(i * FFI::Pointer.size)
|
|
257
|
+
break if p.null?
|
|
258
|
+
|
|
259
|
+
result << p.read_string
|
|
260
|
+
i += 1
|
|
261
|
+
end
|
|
262
|
+
result
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# High-level shaping convenience method
|
|
266
|
+
#
|
|
267
|
+
# @param text [String] Text to shape
|
|
268
|
+
# @param font_path [String] Path to font file
|
|
269
|
+
# @param features [Array<Feature, String>, Hash] Features to apply
|
|
270
|
+
# @param direction [Symbol, nil] Text direction (:ltr, :rtl, etc.)
|
|
271
|
+
# @param script [Integer, nil] Script value
|
|
272
|
+
# @param language [String, nil] BCP 47 language tag (e.g., "en")
|
|
273
|
+
# @return [ShapingResult]
|
|
274
|
+
def self.shape_text(text, font_path:, features: [], direction: nil, script: nil, language: nil)
|
|
275
|
+
blob = Blob.from_file!(font_path)
|
|
276
|
+
face = Face.new(blob, 0)
|
|
277
|
+
font = Font.new(face)
|
|
278
|
+
|
|
279
|
+
buffer = Buffer.new
|
|
280
|
+
buffer.add_utf8(text)
|
|
281
|
+
buffer.direction = direction if direction
|
|
282
|
+
buffer.script = script if script
|
|
283
|
+
buffer.language = self.language(language) if language
|
|
284
|
+
buffer.guess_segment_properties
|
|
285
|
+
|
|
286
|
+
parsed_features = case features
|
|
287
|
+
when Array
|
|
288
|
+
features.map { |f| f.is_a?(Feature) ? f : Feature.from_string(f.to_s) }
|
|
289
|
+
when Hash
|
|
290
|
+
Feature.from_hash(features)
|
|
291
|
+
else
|
|
292
|
+
[]
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
shape(font, buffer, parsed_features)
|
|
296
|
+
ShapingResult.new(buffer: buffer, font: font)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def self.build_features_ptr(features)
|
|
300
|
+
return FFI::Pointer::NULL if features.empty?
|
|
301
|
+
|
|
302
|
+
ptr = FFI::MemoryPointer.new(C::HbFeatureT, features.size)
|
|
303
|
+
features.each_with_index do |f, i|
|
|
304
|
+
s = f.to_struct
|
|
305
|
+
ptr.put_bytes(i * C::HbFeatureT.size, s.to_ptr.read_bytes(C::HbFeatureT.size))
|
|
306
|
+
end
|
|
307
|
+
ptr
|
|
308
|
+
end
|
|
309
|
+
private_class_method :build_features_ptr
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Alias for compatibility (Harfbuzz::VERSION was previously defined here)
|
|
313
|
+
Harfbuzz = HarfBuzz unless defined?(Harfbuzz)
|