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
data/lib/harfbuzz/set.rb
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HarfBuzz
|
|
4
|
+
# Wraps hb_set_t — a set of Unicode codepoints or glyph IDs
|
|
5
|
+
#
|
|
6
|
+
# Includes Enumerable for Ruby-friendly iteration.
|
|
7
|
+
class Set
|
|
8
|
+
include Enumerable
|
|
9
|
+
|
|
10
|
+
HB_SET_VALUE_INVALID = C::HB_SET_VALUE_INVALID
|
|
11
|
+
|
|
12
|
+
attr_reader :ptr
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@ptr = C.hb_set_create
|
|
16
|
+
raise AllocationError, "Failed to create set" if @ptr.null?
|
|
17
|
+
|
|
18
|
+
HarfBuzz::Set.define_finalizer(self, @ptr)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Returns the singleton empty set
|
|
22
|
+
# @return [Set]
|
|
23
|
+
def self.empty
|
|
24
|
+
wrap_borrowed(C.hb_set_get_empty)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Integer] Number of elements
|
|
28
|
+
def size
|
|
29
|
+
C.hb_set_get_population(@ptr)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
alias length size
|
|
33
|
+
|
|
34
|
+
# @return [Boolean] true if empty
|
|
35
|
+
def empty?
|
|
36
|
+
C.from_hb_bool(C.hb_set_is_empty(@ptr))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @param value [Integer] Codepoint to check
|
|
40
|
+
# @return [Boolean] true if value is in the set
|
|
41
|
+
def include?(value)
|
|
42
|
+
C.from_hb_bool(C.hb_set_has(@ptr, value))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
alias member? include?
|
|
46
|
+
|
|
47
|
+
# Adds a value to the set
|
|
48
|
+
# @param value [Integer] Codepoint to add
|
|
49
|
+
# @return [self]
|
|
50
|
+
def add(value)
|
|
51
|
+
C.hb_set_add(@ptr, value)
|
|
52
|
+
self
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
alias << add
|
|
56
|
+
|
|
57
|
+
# Adds a range of values (inclusive)
|
|
58
|
+
# @param first [Integer] First codepoint
|
|
59
|
+
# @param last [Integer] Last codepoint
|
|
60
|
+
# @return [self]
|
|
61
|
+
def add_range(first, last)
|
|
62
|
+
C.hb_set_add_range(@ptr, first, last)
|
|
63
|
+
self
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Adds a sorted array of codepoints
|
|
67
|
+
# @param arr [Array<Integer>] Sorted array of codepoints
|
|
68
|
+
# @return [self]
|
|
69
|
+
def add_sorted_array(arr)
|
|
70
|
+
mem = FFI::MemoryPointer.new(:uint32, arr.size)
|
|
71
|
+
mem.put_array_of_uint32(0, arr)
|
|
72
|
+
C.hb_set_add_sorted_array(@ptr, mem, arr.size)
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Removes a value
|
|
77
|
+
# @param value [Integer] Codepoint to remove
|
|
78
|
+
# @return [self]
|
|
79
|
+
def delete(value)
|
|
80
|
+
C.hb_set_del(@ptr, value)
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Removes a range of values (inclusive)
|
|
85
|
+
# @param first [Integer] First codepoint
|
|
86
|
+
# @param last [Integer] Last codepoint
|
|
87
|
+
# @return [self]
|
|
88
|
+
def delete_range(first, last)
|
|
89
|
+
C.hb_set_del_range(@ptr, first, last)
|
|
90
|
+
self
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Clears all values
|
|
94
|
+
# @return [self]
|
|
95
|
+
def clear
|
|
96
|
+
C.hb_set_clear(@ptr)
|
|
97
|
+
self
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @param other [Set] Set to compare
|
|
101
|
+
# @return [Boolean] true if equal
|
|
102
|
+
def ==(other)
|
|
103
|
+
return false unless other.is_a?(Set)
|
|
104
|
+
|
|
105
|
+
C.from_hb_bool(C.hb_set_is_equal(@ptr, other.ptr))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @return [Integer] Hash of this set
|
|
109
|
+
def hash
|
|
110
|
+
C.hb_set_hash(@ptr)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @param other [Set] Potential superset
|
|
114
|
+
# @return [Boolean] true if self is a subset of other
|
|
115
|
+
def subset?(other)
|
|
116
|
+
C.from_hb_bool(C.hb_set_is_subset(@ptr, other.ptr))
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Replaces contents with another set
|
|
120
|
+
# @param other [Set] Source set
|
|
121
|
+
# @return [self]
|
|
122
|
+
def replace(other)
|
|
123
|
+
C.hb_set_set(@ptr, other.ptr)
|
|
124
|
+
self
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Adds all elements from another set (in-place union)
|
|
128
|
+
# @param other [Set] Set to union with
|
|
129
|
+
# @return [self]
|
|
130
|
+
def union(other)
|
|
131
|
+
result = dup_set
|
|
132
|
+
C.hb_set_union(result.ptr, other.ptr)
|
|
133
|
+
result
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
alias | union
|
|
137
|
+
|
|
138
|
+
# Removes all elements not in another set (in-place intersection)
|
|
139
|
+
# @param other [Set] Set to intersect with
|
|
140
|
+
# @return [self]
|
|
141
|
+
def intersect(other)
|
|
142
|
+
result = dup_set
|
|
143
|
+
C.hb_set_intersect(result.ptr, other.ptr)
|
|
144
|
+
result
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
alias & intersect
|
|
148
|
+
|
|
149
|
+
# Removes all elements that are in another set (in-place subtraction)
|
|
150
|
+
# @param other [Set] Set to subtract
|
|
151
|
+
# @return [self]
|
|
152
|
+
def subtract(other)
|
|
153
|
+
result = dup_set
|
|
154
|
+
C.hb_set_subtract(result.ptr, other.ptr)
|
|
155
|
+
result
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
alias - subtract
|
|
159
|
+
|
|
160
|
+
# Symmetric difference (elements in either but not both)
|
|
161
|
+
# @param other [Set] Other set
|
|
162
|
+
# @return [Set] New set
|
|
163
|
+
def symmetric_difference(other)
|
|
164
|
+
result = dup_set
|
|
165
|
+
C.hb_set_symmetric_difference(result.ptr, other.ptr)
|
|
166
|
+
result
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
alias ^ symmetric_difference
|
|
170
|
+
|
|
171
|
+
# Inverts the set (complements it)
|
|
172
|
+
# @return [self]
|
|
173
|
+
def invert!
|
|
174
|
+
C.hb_set_invert(@ptr)
|
|
175
|
+
self
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @return [Integer, nil] Minimum value or nil if empty
|
|
179
|
+
def min
|
|
180
|
+
val = C.hb_set_get_min(@ptr)
|
|
181
|
+
val == HB_SET_VALUE_INVALID ? nil : val
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# @return [Integer, nil] Maximum value or nil if empty
|
|
185
|
+
def max
|
|
186
|
+
val = C.hb_set_get_max(@ptr)
|
|
187
|
+
val == HB_SET_VALUE_INVALID ? nil : val
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Iterates over all values in ascending order
|
|
191
|
+
# @yield [value]
|
|
192
|
+
# @return [Enumerator] if no block given
|
|
193
|
+
def each
|
|
194
|
+
return to_enum(:each) unless block_given?
|
|
195
|
+
|
|
196
|
+
cp_ptr = FFI::MemoryPointer.new(:uint32)
|
|
197
|
+
cp_ptr.write_uint32(HB_SET_VALUE_INVALID)
|
|
198
|
+
yield cp_ptr.read_uint32 while C.from_hb_bool(C.hb_set_next(@ptr, cp_ptr))
|
|
199
|
+
self
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Iterates over contiguous ranges in ascending order
|
|
203
|
+
# @yield [first, last] First and last value of each range (inclusive)
|
|
204
|
+
# @return [Enumerator] if no block given
|
|
205
|
+
def each_range
|
|
206
|
+
return to_enum(:each_range) unless block_given?
|
|
207
|
+
|
|
208
|
+
first_ptr = FFI::MemoryPointer.new(:uint32)
|
|
209
|
+
last_ptr = FFI::MemoryPointer.new(:uint32)
|
|
210
|
+
first_ptr.write_uint32(HB_SET_VALUE_INVALID)
|
|
211
|
+
last_ptr.write_uint32(HB_SET_VALUE_INVALID)
|
|
212
|
+
while C.from_hb_bool(C.hb_set_next_range(@ptr, first_ptr, last_ptr))
|
|
213
|
+
yield first_ptr.read_uint32, last_ptr.read_uint32
|
|
214
|
+
end
|
|
215
|
+
self
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Iterates over all values in descending order
|
|
219
|
+
# @yield [value]
|
|
220
|
+
# @return [Enumerator] if no block given
|
|
221
|
+
def reverse_each
|
|
222
|
+
return to_enum(:reverse_each) unless block_given?
|
|
223
|
+
|
|
224
|
+
cp_ptr = FFI::MemoryPointer.new(:uint32)
|
|
225
|
+
cp_ptr.write_uint32(HB_SET_VALUE_INVALID)
|
|
226
|
+
yield cp_ptr.read_uint32 while C.from_hb_bool(C.hb_set_previous(@ptr, cp_ptr))
|
|
227
|
+
self
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Bulk-retrieves all values as an array
|
|
231
|
+
# @return [Array<Integer>]
|
|
232
|
+
def to_a
|
|
233
|
+
return [] if empty?
|
|
234
|
+
|
|
235
|
+
count = size
|
|
236
|
+
buf = FFI::MemoryPointer.new(:uint32, count)
|
|
237
|
+
actual = C.hb_set_next_many(@ptr, HB_SET_VALUE_INVALID, buf, count)
|
|
238
|
+
buf.read_array_of_uint32(actual)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def inspect
|
|
242
|
+
"#<HarfBuzz::Set size=#{size}>"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def self.wrap_owned(ptr)
|
|
246
|
+
obj = allocate
|
|
247
|
+
obj.instance_variable_set(:@ptr, ptr)
|
|
248
|
+
define_finalizer(obj, ptr)
|
|
249
|
+
obj
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def self.wrap_borrowed(ptr)
|
|
253
|
+
obj = allocate
|
|
254
|
+
obj.instance_variable_set(:@ptr, ptr)
|
|
255
|
+
obj.instance_variable_set(:@borrowed, true)
|
|
256
|
+
obj
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def self.define_finalizer(obj, ptr)
|
|
260
|
+
destroy = C.method(:hb_set_destroy)
|
|
261
|
+
ObjectSpace.define_finalizer(obj, proc { destroy.call(ptr) })
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
private
|
|
265
|
+
|
|
266
|
+
def dup_set
|
|
267
|
+
new_set = self.class.new
|
|
268
|
+
C.hb_set_set(new_set.ptr, @ptr)
|
|
269
|
+
new_set
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HarfBuzz
|
|
4
|
+
# Wraps hb_shape_plan_t — a cached shaping plan for repeated shaping
|
|
5
|
+
class ShapePlan
|
|
6
|
+
attr_reader :ptr
|
|
7
|
+
|
|
8
|
+
# Creates a new ShapePlan for the given face and properties
|
|
9
|
+
# @param face [Face] Font face
|
|
10
|
+
# @param props [C::HbSegmentPropertiesT] Segment properties
|
|
11
|
+
# @param features [Array<Feature>] Features
|
|
12
|
+
# @param shapers [Array<String>, nil] Shaper list
|
|
13
|
+
# @param coords [Array<Integer>, nil] Normalized variation coordinates (nil = none)
|
|
14
|
+
# @return [ShapePlan]
|
|
15
|
+
def self.new(face, props, features = [], shapers: nil, coords: nil)
|
|
16
|
+
features_ptr = HarfBuzz.send(:build_features_ptr, features)
|
|
17
|
+
shapers_ptr = build_shapers_ptr(shapers)
|
|
18
|
+
if coords
|
|
19
|
+
coords_ptr = FFI::MemoryPointer.new(:int32, coords.size)
|
|
20
|
+
coords_ptr.put_array_of_int32(0, coords)
|
|
21
|
+
ptr = C.hb_shape_plan_create2(
|
|
22
|
+
face.ptr, props, features_ptr, features.size,
|
|
23
|
+
shapers_ptr, coords.size, coords_ptr
|
|
24
|
+
)
|
|
25
|
+
else
|
|
26
|
+
ptr = C.hb_shape_plan_create(
|
|
27
|
+
face.ptr, props, features_ptr, features.size, shapers_ptr
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
wrap_owned(ptr)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Creates (or retrieves from cache) a ShapePlan
|
|
34
|
+
# @param face [Face] Font face
|
|
35
|
+
# @param props [C::HbSegmentPropertiesT] Segment properties
|
|
36
|
+
# @param features [Array<Feature>] Features
|
|
37
|
+
# @param shapers [Array<String>, nil] Shaper list
|
|
38
|
+
# @param coords [Array<Integer>, nil] Normalized variation coordinates (nil = none)
|
|
39
|
+
# @return [ShapePlan]
|
|
40
|
+
def self.cached(face, props, features = [], shapers: nil, coords: nil)
|
|
41
|
+
features_ptr = HarfBuzz.send(:build_features_ptr, features)
|
|
42
|
+
shapers_ptr = build_shapers_ptr(shapers)
|
|
43
|
+
if coords
|
|
44
|
+
coords_ptr = FFI::MemoryPointer.new(:int32, coords.size)
|
|
45
|
+
coords_ptr.put_array_of_int32(0, coords)
|
|
46
|
+
ptr = C.hb_shape_plan_create_cached2(
|
|
47
|
+
face.ptr, props, features_ptr, features.size,
|
|
48
|
+
shapers_ptr, coords.size, coords_ptr
|
|
49
|
+
)
|
|
50
|
+
else
|
|
51
|
+
ptr = C.hb_shape_plan_create_cached(
|
|
52
|
+
face.ptr, props, features_ptr, features.size, shapers_ptr
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
wrap_owned(ptr)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns the singleton empty shape plan
|
|
59
|
+
# @return [ShapePlan]
|
|
60
|
+
def self.empty
|
|
61
|
+
wrap_borrowed(C.hb_shape_plan_get_empty)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Executes the shape plan
|
|
65
|
+
# @param font [Font] Font to use
|
|
66
|
+
# @param buffer [Buffer] Buffer to shape
|
|
67
|
+
# @param features [Array<Feature>] Features
|
|
68
|
+
# @return [Boolean] Success
|
|
69
|
+
def execute(font, buffer, features = [])
|
|
70
|
+
features_ptr = HarfBuzz.send(:build_features_ptr, features)
|
|
71
|
+
C.from_hb_bool(
|
|
72
|
+
C.hb_shape_plan_execute(@ptr, font.ptr, buffer.ptr, features_ptr, features.size)
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @return [String] Name of the shaper used
|
|
77
|
+
def shaper
|
|
78
|
+
C.hb_shape_plan_get_shaper(@ptr)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def inspect
|
|
82
|
+
"#<HarfBuzz::ShapePlan shaper=#{shaper.inspect}>"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.wrap_owned(ptr)
|
|
86
|
+
obj = allocate
|
|
87
|
+
obj.instance_variable_set(:@ptr, ptr)
|
|
88
|
+
define_finalizer(obj, ptr)
|
|
89
|
+
obj
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.wrap_borrowed(ptr)
|
|
93
|
+
obj = allocate
|
|
94
|
+
obj.instance_variable_set(:@ptr, ptr)
|
|
95
|
+
obj.instance_variable_set(:@borrowed, true)
|
|
96
|
+
obj
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.define_finalizer(obj, ptr)
|
|
100
|
+
destroy = C.method(:hb_shape_plan_destroy)
|
|
101
|
+
ObjectSpace.define_finalizer(obj, proc { destroy.call(ptr) })
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.build_shapers_ptr(shapers)
|
|
105
|
+
return FFI::Pointer::NULL unless shapers
|
|
106
|
+
|
|
107
|
+
ptrs = shapers.map { |s| FFI::MemoryPointer.from_string(s) }
|
|
108
|
+
ptrs << FFI::Pointer::NULL
|
|
109
|
+
arr = FFI::MemoryPointer.new(:pointer, ptrs.size)
|
|
110
|
+
ptrs.each_with_index { |p, i| arr[i].put_pointer(0, p) }
|
|
111
|
+
arr
|
|
112
|
+
end
|
|
113
|
+
private_class_method :build_shapers_ptr
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HarfBuzz
|
|
4
|
+
# Value object returned by HarfBuzz.shape_text — provides convenient access
|
|
5
|
+
# to shaped glyphs, positions, and rendering helpers.
|
|
6
|
+
class ShapingResult
|
|
7
|
+
include Enumerable
|
|
8
|
+
|
|
9
|
+
attr_reader :buffer, :font
|
|
10
|
+
|
|
11
|
+
# @param buffer [Buffer] Shaped buffer
|
|
12
|
+
# @param font [Font] Font used for shaping
|
|
13
|
+
def initialize(buffer:, font:)
|
|
14
|
+
@buffer = buffer
|
|
15
|
+
@font = font
|
|
16
|
+
@glyph_infos = buffer.glyph_infos
|
|
17
|
+
@glyph_positions = buffer.glyph_positions
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [Array<GlyphInfo>] Glyph info array
|
|
21
|
+
def glyph_infos
|
|
22
|
+
@glyph_infos
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @return [Array<GlyphPosition>] Glyph position array
|
|
26
|
+
def glyph_positions
|
|
27
|
+
@glyph_positions
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Iterates over [GlyphInfo, GlyphPosition] pairs
|
|
31
|
+
def each
|
|
32
|
+
@glyph_infos.zip(@glyph_positions).each { |info, pos| yield info, pos }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Integer] Number of glyphs
|
|
36
|
+
def length
|
|
37
|
+
@glyph_infos.length
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
alias size length
|
|
41
|
+
|
|
42
|
+
# Returns the total advance as [x_advance, y_advance]
|
|
43
|
+
# @return [Array<Integer>]
|
|
44
|
+
def total_advance
|
|
45
|
+
@glyph_positions.inject([0, 0]) do |(x, y), pos|
|
|
46
|
+
[x + pos.x_advance, y + pos.y_advance]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Generates an SVG path string for all glyphs at their shaped positions
|
|
51
|
+
# @return [String] SVG path data
|
|
52
|
+
def to_svg_path
|
|
53
|
+
paths = []
|
|
54
|
+
cx = 0
|
|
55
|
+
cy = 0
|
|
56
|
+
each do |info, pos|
|
|
57
|
+
glyph_path = extract_glyph_path(info.glyph_id)
|
|
58
|
+
ox = cx + pos.x_offset
|
|
59
|
+
oy = cy + pos.y_offset
|
|
60
|
+
paths << translate_path(glyph_path, ox, oy) unless glyph_path.empty?
|
|
61
|
+
cx += pos.x_advance
|
|
62
|
+
cy += pos.y_advance
|
|
63
|
+
end
|
|
64
|
+
paths.join(" ")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def inspect
|
|
68
|
+
"#<HarfBuzz::ShapingResult length=#{length} total_advance=#{total_advance.inspect}>"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def extract_glyph_path(glyph_id)
|
|
74
|
+
segments = []
|
|
75
|
+
draw = DrawFuncs.new
|
|
76
|
+
draw.on_move_to { |x, y| segments << "M#{x},#{y}" }
|
|
77
|
+
draw.on_line_to { |x, y| segments << "L#{x},#{y}" }
|
|
78
|
+
draw.on_quadratic_to { |cx, cy, x, y| segments << "Q#{cx},#{cy},#{x},#{y}" }
|
|
79
|
+
draw.on_cubic_to { |c1x, c1y, c2x, c2y, x, y|
|
|
80
|
+
segments << "C#{c1x},#{c1y},#{c2x},#{c2y},#{x},#{y}"
|
|
81
|
+
}
|
|
82
|
+
draw.on_close_path { segments << "Z" }
|
|
83
|
+
draw.make_immutable!
|
|
84
|
+
@font.draw_glyph(glyph_id, draw)
|
|
85
|
+
segments.join
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def translate_path(path, ox, oy)
|
|
89
|
+
return path if ox.zero? && oy.zero?
|
|
90
|
+
|
|
91
|
+
"translate(#{ox},#{oy}) #{path}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HarfBuzz
|
|
4
|
+
# Font subsetting API (requires libharfbuzz-subset)
|
|
5
|
+
module Subset
|
|
6
|
+
# @return [Boolean] true if libharfbuzz-subset is available
|
|
7
|
+
def self.available?
|
|
8
|
+
C.available?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Subsets a font face using the given input specification
|
|
12
|
+
# @param face [HarfBuzz::Face] Source font face
|
|
13
|
+
# @param input [Input] Subset input specification
|
|
14
|
+
# @return [HarfBuzz::Face] Subsetted face
|
|
15
|
+
# @raise [UnavailableError] If subset library is not available
|
|
16
|
+
# @raise [SubsetError] If subsetting fails
|
|
17
|
+
def self.subset(face, input)
|
|
18
|
+
raise UnavailableError, "libharfbuzz-subset is not available" unless available?
|
|
19
|
+
|
|
20
|
+
ptr = C.hb_subset_or_fail(face.ptr, input.ptr)
|
|
21
|
+
raise SubsetError, "Subsetting failed" if ptr.null?
|
|
22
|
+
|
|
23
|
+
HarfBuzz::Face.wrap_owned(ptr)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Wraps hb_subset_input_t — specifies what to include in a subset
|
|
27
|
+
class Input
|
|
28
|
+
attr_reader :ptr
|
|
29
|
+
|
|
30
|
+
# Creates a new subset input
|
|
31
|
+
# @raise [UnavailableError] If subset library is not available
|
|
32
|
+
# @raise [AllocationError] If allocation fails
|
|
33
|
+
def initialize
|
|
34
|
+
raise UnavailableError, "libharfbuzz-subset is not available" unless C.available?
|
|
35
|
+
|
|
36
|
+
@ptr = C.hb_subset_input_create_or_fail
|
|
37
|
+
raise AllocationError, "Failed to create subset input" if @ptr.null?
|
|
38
|
+
|
|
39
|
+
HarfBuzz::Subset::Input.define_finalizer(self, @ptr)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns the set of Unicode codepoints to include (borrowed reference)
|
|
43
|
+
# @return [HarfBuzz::Set] Unicode set (do not destroy separately)
|
|
44
|
+
def unicode_set
|
|
45
|
+
HarfBuzz::Set.wrap_borrowed(C.hb_subset_input_unicode_set(@ptr))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Returns the set of glyph IDs to include (borrowed reference)
|
|
49
|
+
# @return [HarfBuzz::Set] Glyph set (do not destroy separately)
|
|
50
|
+
def glyph_set
|
|
51
|
+
HarfBuzz::Set.wrap_borrowed(C.hb_subset_input_glyph_set(@ptr))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [Integer] Current flags bitmask
|
|
55
|
+
def flags
|
|
56
|
+
C.hb_subset_input_get_flags(@ptr)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @param flags [Integer] New flags bitmask
|
|
60
|
+
def flags=(flags)
|
|
61
|
+
C.hb_subset_input_set_flags(@ptr, flags)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def inspect
|
|
65
|
+
"#<HarfBuzz::Subset::Input flags=#{flags}>"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.define_finalizer(obj, ptr)
|
|
69
|
+
destroy = C.method(:hb_subset_input_destroy)
|
|
70
|
+
ObjectSpace.define_finalizer(obj, proc { destroy.call(ptr) })
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Wraps hb_subset_plan_t — a more fine-grained subsetting plan
|
|
75
|
+
class Plan
|
|
76
|
+
attr_reader :ptr
|
|
77
|
+
|
|
78
|
+
# Creates a subset plan for a face using the given input
|
|
79
|
+
# @param face [HarfBuzz::Face] Source font face
|
|
80
|
+
# @param input [Input] Subset input specification
|
|
81
|
+
# @raise [UnavailableError] If subset library is not available
|
|
82
|
+
# @raise [SubsetError] If plan creation fails
|
|
83
|
+
def initialize(face, input)
|
|
84
|
+
raise UnavailableError, "libharfbuzz-subset is not available" unless C.available?
|
|
85
|
+
|
|
86
|
+
@ptr = C.hb_subset_plan_create_or_fail(face.ptr, input.ptr)
|
|
87
|
+
raise SubsetError, "Failed to create subset plan" if @ptr.null?
|
|
88
|
+
|
|
89
|
+
HarfBuzz::Subset::Plan.define_finalizer(self, @ptr)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Executes the plan and returns the subsetted face
|
|
93
|
+
# @return [HarfBuzz::Face] Subsetted face
|
|
94
|
+
# @raise [SubsetError] If execution fails
|
|
95
|
+
def execute
|
|
96
|
+
ptr = C.hb_subset_plan_execute_or_fail(@ptr)
|
|
97
|
+
raise SubsetError, "Subset plan execution failed" if ptr.null?
|
|
98
|
+
|
|
99
|
+
HarfBuzz::Face.wrap_owned(ptr)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Returns the old→new glyph ID mapping (borrowed)
|
|
103
|
+
# @return [HarfBuzz::Map]
|
|
104
|
+
def old_to_new_glyph_mapping
|
|
105
|
+
HarfBuzz::Map.wrap_borrowed(C.hb_subset_plan_old_to_new_glyph_mapping(@ptr))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Returns the new→old glyph ID mapping (borrowed)
|
|
109
|
+
# @return [HarfBuzz::Map]
|
|
110
|
+
def new_to_old_glyph_mapping
|
|
111
|
+
HarfBuzz::Map.wrap_borrowed(C.hb_subset_plan_new_to_old_glyph_mapping(@ptr))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Returns the unicode→old glyph ID mapping (borrowed)
|
|
115
|
+
# @return [HarfBuzz::Map]
|
|
116
|
+
def unicode_to_old_glyph_mapping
|
|
117
|
+
HarfBuzz::Map.wrap_borrowed(C.hb_subset_plan_unicode_to_old_glyph_mapping(@ptr))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def inspect
|
|
121
|
+
"#<HarfBuzz::Subset::Plan>"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def self.define_finalizer(obj, ptr)
|
|
125
|
+
destroy = C.method(:hb_subset_plan_destroy)
|
|
126
|
+
ObjectSpace.define_finalizer(obj, proc { destroy.call(ptr) })
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|