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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +258 -0
  5. data/Rakefile +8 -0
  6. data/benchmark/shaping_bench.rb +77 -0
  7. data/examples/basic_shaping.rb +67 -0
  8. data/examples/glyph_outlines.rb +79 -0
  9. data/examples/opentype_features.rb +91 -0
  10. data/examples/render_svg.rb +112 -0
  11. data/examples/render_waterfall.rb +177 -0
  12. data/examples/variable_fonts.rb +73 -0
  13. data/lib/harfbuzz/aat/layout.rb +78 -0
  14. data/lib/harfbuzz/blob.rb +136 -0
  15. data/lib/harfbuzz/buffer.rb +497 -0
  16. data/lib/harfbuzz/c/aat/layout.rb +15 -0
  17. data/lib/harfbuzz/c/base.rb +114 -0
  18. data/lib/harfbuzz/c/blob.rb +23 -0
  19. data/lib/harfbuzz/c/buffer.rb +127 -0
  20. data/lib/harfbuzz/c/common.rb +39 -0
  21. data/lib/harfbuzz/c/draw.rb +22 -0
  22. data/lib/harfbuzz/c/enums.rb +146 -0
  23. data/lib/harfbuzz/c/face.rb +37 -0
  24. data/lib/harfbuzz/c/font.rb +88 -0
  25. data/lib/harfbuzz/c/font_funcs.rb +58 -0
  26. data/lib/harfbuzz/c/map.rb +28 -0
  27. data/lib/harfbuzz/c/ot/color.rb +32 -0
  28. data/lib/harfbuzz/c/ot/font.rb +7 -0
  29. data/lib/harfbuzz/c/ot/layout.rb +83 -0
  30. data/lib/harfbuzz/c/ot/math.rb +23 -0
  31. data/lib/harfbuzz/c/ot/meta.rb +10 -0
  32. data/lib/harfbuzz/c/ot/metrics.rb +16 -0
  33. data/lib/harfbuzz/c/ot/name.rb +13 -0
  34. data/lib/harfbuzz/c/ot/shape.rb +10 -0
  35. data/lib/harfbuzz/c/ot/var.rb +22 -0
  36. data/lib/harfbuzz/c/paint.rb +38 -0
  37. data/lib/harfbuzz/c/set.rb +42 -0
  38. data/lib/harfbuzz/c/shape.rb +11 -0
  39. data/lib/harfbuzz/c/shape_plan.rb +24 -0
  40. data/lib/harfbuzz/c/structs.rb +120 -0
  41. data/lib/harfbuzz/c/subset.rb +49 -0
  42. data/lib/harfbuzz/c/unicode.rb +40 -0
  43. data/lib/harfbuzz/c/version.rb +25 -0
  44. data/lib/harfbuzz/draw_funcs.rb +112 -0
  45. data/lib/harfbuzz/error.rb +27 -0
  46. data/lib/harfbuzz/face.rb +186 -0
  47. data/lib/harfbuzz/feature.rb +76 -0
  48. data/lib/harfbuzz/flags.rb +85 -0
  49. data/lib/harfbuzz/font.rb +404 -0
  50. data/lib/harfbuzz/font_funcs.rb +286 -0
  51. data/lib/harfbuzz/glyph_info.rb +35 -0
  52. data/lib/harfbuzz/glyph_position.rb +41 -0
  53. data/lib/harfbuzz/library.rb +98 -0
  54. data/lib/harfbuzz/map.rb +157 -0
  55. data/lib/harfbuzz/ot/color.rb +125 -0
  56. data/lib/harfbuzz/ot/font.rb +16 -0
  57. data/lib/harfbuzz/ot/layout.rb +583 -0
  58. data/lib/harfbuzz/ot/math.rb +111 -0
  59. data/lib/harfbuzz/ot/meta.rb +34 -0
  60. data/lib/harfbuzz/ot/metrics.rb +54 -0
  61. data/lib/harfbuzz/ot/name.rb +81 -0
  62. data/lib/harfbuzz/ot/shape.rb +34 -0
  63. data/lib/harfbuzz/ot/var.rb +116 -0
  64. data/lib/harfbuzz/paint_funcs.rb +134 -0
  65. data/lib/harfbuzz/set.rb +272 -0
  66. data/lib/harfbuzz/shape_plan.rb +115 -0
  67. data/lib/harfbuzz/shaping_result.rb +94 -0
  68. data/lib/harfbuzz/subset.rb +130 -0
  69. data/lib/harfbuzz/unicode_funcs.rb +201 -0
  70. data/lib/harfbuzz/variation.rb +49 -0
  71. data/lib/harfbuzz/version.rb +5 -0
  72. data/lib/harfbuzz-ffi.rb +4 -0
  73. data/lib/harfbuzz.rb +313 -0
  74. data/sig/harfbuzz.rbs +594 -0
  75. metadata +132 -0
@@ -0,0 +1,404 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ # Wraps hb_font_t — a sized, positioned font instance
5
+ class Font
6
+ attr_reader :ptr
7
+
8
+ # Creates a Font from a Face
9
+ # @param face [Face] Font face
10
+ def initialize(face)
11
+ @face = face
12
+ @ptr = C.hb_font_create(face.ptr)
13
+ raise AllocationError, "Failed to create font" if @ptr.null?
14
+
15
+ register_finalizer
16
+ end
17
+
18
+ # Attaches a FontFuncs callback table to this font
19
+ # @param funcs [FontFuncs] Font funcs object (should be immutable)
20
+ # @param font_data [FFI::Pointer] User data pointer passed to callbacks (optional)
21
+ # @return [self]
22
+ def set_funcs(funcs, font_data: FFI::Pointer::NULL)
23
+ C.hb_font_set_funcs(@ptr, funcs.ptr, font_data, nil)
24
+ @funcs = funcs # keep alive
25
+ self
26
+ end
27
+
28
+ # Sets the font funcs (setter alias for set_funcs without font_data)
29
+ # @param funcs [FontFuncs] FontFuncs object
30
+ def funcs=(funcs)
31
+ set_funcs(funcs)
32
+ end
33
+
34
+ # Creates a sub-font of this font
35
+ # @return [Font]
36
+ def create_sub_font
37
+ ptr = C.hb_font_create_sub_font(@ptr)
38
+ self.class.wrap_owned(ptr)
39
+ end
40
+
41
+ # Returns the singleton empty font
42
+ # @return [Font]
43
+ def self.empty
44
+ wrap_borrowed(C.hb_font_get_empty)
45
+ end
46
+
47
+ # Returns the face this font was created from (borrowed reference)
48
+ # @return [Face]
49
+ def face
50
+ ptr = C.hb_font_get_face(@ptr)
51
+ Face.wrap_borrowed(ptr)
52
+ end
53
+
54
+ # Returns scale as [x_scale, y_scale]
55
+ # @return [Array<Integer>]
56
+ def scale
57
+ x_ptr = FFI::MemoryPointer.new(:int)
58
+ y_ptr = FFI::MemoryPointer.new(:int)
59
+ C.hb_font_get_scale(@ptr, x_ptr, y_ptr)
60
+ [x_ptr.read_int, y_ptr.read_int]
61
+ end
62
+
63
+ # Sets font scale
64
+ # @param xy [Array<Integer>, Integer] [x, y] or single value for both
65
+ def scale=(xy)
66
+ x, y = Array(xy).length == 2 ? xy : [xy, xy]
67
+ C.hb_font_set_scale(@ptr, x, y)
68
+ end
69
+
70
+ # Returns pixels per em as [x_ppem, y_ppem]
71
+ # @return [Array<Integer>]
72
+ def ppem
73
+ x_ptr = FFI::MemoryPointer.new(:uint)
74
+ y_ptr = FFI::MemoryPointer.new(:uint)
75
+ C.hb_font_get_ppem(@ptr, x_ptr, y_ptr)
76
+ [x_ptr.read_uint, y_ptr.read_uint]
77
+ end
78
+
79
+ # Sets pixels per em
80
+ # @param xy [Array<Integer>] [x_ppem, y_ppem]
81
+ def ppem=(xy)
82
+ x, y = xy
83
+ C.hb_font_set_ppem(@ptr, x, y)
84
+ end
85
+
86
+ # @return [Float] Points per em
87
+ def ptem
88
+ C.hb_font_get_ptem(@ptr)
89
+ end
90
+
91
+ # @param pt [Float] Points per em
92
+ def ptem=(pt)
93
+ C.hb_font_set_ptem(@ptr, pt)
94
+ end
95
+
96
+ # Returns synthetic bold as [x_embolden, y_embolden, in_place]
97
+ # @return [Array]
98
+ def synthetic_bold
99
+ x_ptr = FFI::MemoryPointer.new(:float)
100
+ y_ptr = FFI::MemoryPointer.new(:float)
101
+ ip_ptr = FFI::MemoryPointer.new(:int)
102
+ C.hb_font_get_synthetic_bold(@ptr, x_ptr, y_ptr, ip_ptr)
103
+ [x_ptr.read_float, y_ptr.read_float, C.from_hb_bool(ip_ptr.read_int)]
104
+ end
105
+
106
+ # Sets synthetic bold
107
+ # @param xyz [Array] [x_embolden, y_embolden, in_place]
108
+ def synthetic_bold=(xyz)
109
+ x, y, ip = xyz
110
+ C.hb_font_set_synthetic_bold(@ptr, x, y, C.to_hb_bool(ip))
111
+ end
112
+
113
+ # @return [Float] Synthetic slant
114
+ def synthetic_slant
115
+ C.hb_font_get_synthetic_slant(@ptr)
116
+ end
117
+
118
+ # @param slant [Float] Synthetic slant
119
+ def synthetic_slant=(slant)
120
+ C.hb_font_set_synthetic_slant(@ptr, slant)
121
+ end
122
+
123
+ # Sets variation axis values from an array of Variation objects
124
+ # @param variations [Array<Variation>]
125
+ def variations=(variations)
126
+ structs = variations.map(&:to_struct)
127
+ ptr = FFI::MemoryPointer.new(C::HbVariationT, structs.size)
128
+ structs.each_with_index do |s, i|
129
+ ptr.put_bytes(i * C::HbVariationT.size,
130
+ s.to_ptr.read_bytes(C::HbVariationT.size))
131
+ end
132
+ C.hb_font_set_variations(@ptr, ptr, structs.size)
133
+ end
134
+
135
+ # Sets a single variation axis value
136
+ # @param tag [Integer] Axis tag
137
+ # @param value [Float] Axis value
138
+ def set_variation(tag, value)
139
+ C.hb_font_set_variation(@ptr, tag, value)
140
+ end
141
+
142
+ # Sets design-space variation coordinates
143
+ # @param coords [Array<Float>]
144
+ def var_coords_design=(coords)
145
+ ptr = FFI::MemoryPointer.new(:float, coords.size)
146
+ ptr.put_array_of_float(0, coords)
147
+ C.hb_font_set_var_coords_design(@ptr, ptr, coords.size)
148
+ end
149
+
150
+ # Returns design-space variation coordinates
151
+ # @return [Array<Float>]
152
+ def var_coords_design
153
+ count_ptr = FFI::MemoryPointer.new(:uint)
154
+ coords_ptr = C.hb_font_get_var_coords_design(@ptr, count_ptr)
155
+ count = count_ptr.read_uint
156
+ return [] if coords_ptr.null? || count.zero?
157
+
158
+ coords_ptr.read_array_of_float(count)
159
+ end
160
+
161
+ # Sets normalized variation coordinates
162
+ # @param coords [Array<Integer>]
163
+ def var_coords_normalized=(coords)
164
+ ptr = FFI::MemoryPointer.new(:int32, coords.size)
165
+ ptr.put_array_of_int32(0, coords)
166
+ C.hb_font_set_var_coords_normalized(@ptr, ptr, coords.size)
167
+ end
168
+
169
+ # Returns normalized variation coordinates
170
+ # @return [Array<Integer>]
171
+ def var_coords_normalized
172
+ count_ptr = FFI::MemoryPointer.new(:uint)
173
+ coords_ptr = C.hb_font_get_var_coords_normalized(@ptr, count_ptr)
174
+ count = count_ptr.read_uint
175
+ return [] if coords_ptr.null? || count.zero?
176
+
177
+ coords_ptr.read_array_of_int32(count)
178
+ end
179
+
180
+ # @return [Boolean] true if font is immutable
181
+ def immutable?
182
+ C.from_hb_bool(C.hb_font_is_immutable(@ptr))
183
+ end
184
+
185
+ # Makes the font immutable (thread-safe to share after this)
186
+ # @return [self]
187
+ def make_immutable!
188
+ C.hb_font_make_immutable(@ptr)
189
+ self
190
+ end
191
+
192
+ # Returns glyph ID for a codepoint (with optional variation selector)
193
+ # @param cp [Integer] Unicode codepoint
194
+ # @param variation_selector [Integer] Variation selector codepoint (0 = none)
195
+ # @return [Integer, nil] Glyph ID or nil if not found
196
+ def glyph(cp, variation_selector = 0)
197
+ glyph_ptr = FFI::MemoryPointer.new(:uint32)
198
+ ok = C.hb_font_get_glyph(@ptr, cp, variation_selector, glyph_ptr)
199
+ ok.zero? ? nil : glyph_ptr.read_uint32
200
+ end
201
+
202
+ # Returns glyph ID for a nominal (non-variation) codepoint
203
+ # @param cp [Integer] Unicode codepoint
204
+ # @return [Integer, nil] Glyph ID or nil if not found
205
+ def nominal_glyph(cp)
206
+ glyph_ptr = FFI::MemoryPointer.new(:uint32)
207
+ ok = C.hb_font_get_nominal_glyph(@ptr, cp, glyph_ptr)
208
+ ok.zero? ? nil : glyph_ptr.read_uint32
209
+ end
210
+
211
+ # Returns nominal glyph IDs for multiple codepoints in a single call
212
+ # @param codepoints [Array<Integer>] Unicode codepoints
213
+ # @return [Array<Integer>] Glyph IDs (0 for unmapped codepoints)
214
+ def nominal_glyphs(codepoints)
215
+ count = codepoints.size
216
+ return [] if count.zero?
217
+
218
+ cp_ptr = FFI::MemoryPointer.new(:uint32, count)
219
+ cp_ptr.put_array_of_uint32(0, codepoints)
220
+ glyph_ptr = FFI::MemoryPointer.new(:uint32, count)
221
+ C.hb_font_get_nominal_glyphs(@ptr, count, cp_ptr, 4, glyph_ptr, 4)
222
+ glyph_ptr.read_array_of_uint32(count)
223
+ end
224
+
225
+ # Returns glyph ID for a variation sequence
226
+ # @param cp [Integer] Unicode codepoint
227
+ # @param selector [Integer] Variation selector
228
+ # @return [Integer, nil] Glyph ID or nil if not found
229
+ def variation_glyph(cp, selector)
230
+ glyph_ptr = FFI::MemoryPointer.new(:uint32)
231
+ ok = C.hb_font_get_variation_glyph(@ptr, cp, selector, glyph_ptr)
232
+ ok.zero? ? nil : glyph_ptr.read_uint32
233
+ end
234
+
235
+ # @param glyph [Integer] Glyph ID
236
+ # @return [Integer] Horizontal advance in font units
237
+ def glyph_h_advance(glyph)
238
+ C.hb_font_get_glyph_h_advance(@ptr, glyph)
239
+ end
240
+
241
+ # @param glyph [Integer] Glyph ID
242
+ # @return [Integer] Vertical advance in font units
243
+ def glyph_v_advance(glyph)
244
+ C.hb_font_get_glyph_v_advance(@ptr, glyph)
245
+ end
246
+
247
+ # Returns horizontal advances for multiple glyphs
248
+ # @param glyphs [Array<Integer>] Glyph IDs
249
+ # @return [Array<Integer>] Advances
250
+ def glyph_h_advances(glyphs)
251
+ count = glyphs.size
252
+ glyph_ptr = FFI::MemoryPointer.new(:uint32, count)
253
+ glyph_ptr.put_array_of_uint32(0, glyphs)
254
+ advance_ptr = FFI::MemoryPointer.new(:int32, count)
255
+ C.hb_font_get_glyph_h_advances(@ptr, count, glyph_ptr, 4, advance_ptr, 4)
256
+ advance_ptr.read_array_of_int32(count)
257
+ end
258
+
259
+ # Returns vertical advances for multiple glyphs
260
+ # @param glyphs [Array<Integer>] Glyph IDs
261
+ # @return [Array<Integer>] Advances
262
+ def glyph_v_advances(glyphs)
263
+ count = glyphs.size
264
+ glyph_ptr = FFI::MemoryPointer.new(:uint32, count)
265
+ glyph_ptr.put_array_of_uint32(0, glyphs)
266
+ advance_ptr = FFI::MemoryPointer.new(:int32, count)
267
+ C.hb_font_get_glyph_v_advances(@ptr, count, glyph_ptr, 4, advance_ptr, 4)
268
+ advance_ptr.read_array_of_int32(count)
269
+ end
270
+
271
+ # Returns horizontal origin for a glyph
272
+ # @param glyph [Integer] Glyph ID
273
+ # @return [Array<Integer>, nil] [x, y] or nil
274
+ def glyph_h_origin(glyph)
275
+ x_ptr = FFI::MemoryPointer.new(:int32)
276
+ y_ptr = FFI::MemoryPointer.new(:int32)
277
+ ok = C.hb_font_get_glyph_h_origin(@ptr, glyph, x_ptr, y_ptr)
278
+ ok.zero? ? nil : [x_ptr.read_int32, y_ptr.read_int32]
279
+ end
280
+
281
+ # Returns vertical origin for a glyph
282
+ # @param glyph [Integer] Glyph ID
283
+ # @return [Array<Integer>, nil] [x, y] or nil
284
+ def glyph_v_origin(glyph)
285
+ x_ptr = FFI::MemoryPointer.new(:int32)
286
+ y_ptr = FFI::MemoryPointer.new(:int32)
287
+ ok = C.hb_font_get_glyph_v_origin(@ptr, glyph, x_ptr, y_ptr)
288
+ ok.zero? ? nil : [x_ptr.read_int32, y_ptr.read_int32]
289
+ end
290
+
291
+ # Returns horizontal kerning between two glyphs
292
+ # @param left [Integer] Left glyph ID
293
+ # @param right [Integer] Right glyph ID
294
+ # @return [Integer] Kerning value
295
+ def glyph_h_kerning(left, right)
296
+ C.hb_font_get_glyph_h_kerning(@ptr, left, right)
297
+ end
298
+
299
+ # Returns glyph extents
300
+ # @param glyph [Integer] Glyph ID
301
+ # @return [C::HbGlyphExtentsT, nil] Extents or nil
302
+ def glyph_extents(glyph)
303
+ extents = C::HbGlyphExtentsT.new
304
+ ok = C.hb_font_get_glyph_extents(@ptr, glyph, extents)
305
+ ok.zero? ? nil : extents
306
+ end
307
+
308
+ # Returns a contour point for a glyph
309
+ # @param glyph [Integer] Glyph ID
310
+ # @param idx [Integer] Contour point index
311
+ # @return [Array<Integer>, nil] [x, y] or nil
312
+ def glyph_contour_point(glyph, idx)
313
+ x_ptr = FFI::MemoryPointer.new(:int32)
314
+ y_ptr = FFI::MemoryPointer.new(:int32)
315
+ ok = C.hb_font_get_glyph_contour_point(@ptr, glyph, idx, x_ptr, y_ptr)
316
+ ok.zero? ? nil : [x_ptr.read_int32, y_ptr.read_int32]
317
+ end
318
+
319
+ # Returns the glyph name
320
+ # @param glyph [Integer] Glyph ID
321
+ # @return [String, nil] Glyph name or nil
322
+ def glyph_name(glyph)
323
+ buf = FFI::MemoryPointer.new(:char, 64)
324
+ ok = C.hb_font_get_glyph_name(@ptr, glyph, buf, 64)
325
+ ok.zero? ? nil : buf.read_string
326
+ end
327
+
328
+ # Returns the glyph ID for a name
329
+ # @param name [String] Glyph name
330
+ # @return [Integer, nil] Glyph ID or nil
331
+ def glyph_from_name(name)
332
+ glyph_ptr = FFI::MemoryPointer.new(:uint32)
333
+ ok = C.hb_font_get_glyph_from_name(@ptr, name, name.bytesize, glyph_ptr)
334
+ ok.zero? ? nil : glyph_ptr.read_uint32
335
+ end
336
+
337
+ # Returns glyph advance for a direction as [x, y]
338
+ # @param glyph [Integer] Glyph ID
339
+ # @param dir [Symbol] Direction (:ltr, :rtl, :ttb, :btt)
340
+ # @return [Array<Integer>] [x_advance, y_advance]
341
+ def glyph_advance_for_direction(glyph, dir)
342
+ x_ptr = FFI::MemoryPointer.new(:int32)
343
+ y_ptr = FFI::MemoryPointer.new(:int32)
344
+ C.hb_font_get_glyph_advance_for_direction(@ptr, glyph, dir, x_ptr, y_ptr)
345
+ [x_ptr.read_int32, y_ptr.read_int32]
346
+ end
347
+
348
+ # Returns font extents for a direction
349
+ # @param dir [Symbol] Direction (:ltr, :rtl, :ttb, :btt)
350
+ # @return [C::HbFontExtentsT]
351
+ def extents_for_direction(dir)
352
+ extents = C::HbFontExtentsT.new
353
+ C.hb_font_get_extents_for_direction(@ptr, dir, extents)
354
+ extents
355
+ end
356
+
357
+ # Draws the outline of a glyph using DrawFuncs callbacks
358
+ # @param glyph [Integer] Glyph ID
359
+ # @param draw_funcs [DrawFuncs] Draw callbacks object
360
+ def draw_glyph(glyph, draw_funcs)
361
+ C.hb_font_draw_glyph(@ptr, glyph, draw_funcs.ptr, nil)
362
+ end
363
+
364
+ # Paints a glyph using PaintFuncs callbacks
365
+ # @param glyph [Integer] Glyph ID
366
+ # @param paint_funcs [PaintFuncs] Paint callbacks object
367
+ # @param palette_index [Integer] Color palette index (default 0)
368
+ # @param foreground [Integer] Foreground color as RGBA uint32 (default 0xFF000000)
369
+ def paint_glyph(glyph, paint_funcs, palette_index: 0, foreground: 0xFF000000)
370
+ C.hb_font_paint_glyph(@ptr, glyph, paint_funcs.ptr, nil, palette_index, foreground)
371
+ end
372
+
373
+ def inspect
374
+ "#<HarfBuzz::Font scale=#{scale} ppem=#{ppem} immutable=#{immutable?}>"
375
+ end
376
+
377
+ def self.wrap_owned(ptr)
378
+ obj = allocate
379
+ obj.instance_variable_set(:@ptr, ptr)
380
+ obj.send(:register_finalizer)
381
+ obj
382
+ end
383
+
384
+ def self.wrap_borrowed(ptr)
385
+ obj = allocate
386
+ obj.instance_variable_set(:@ptr, ptr)
387
+ obj.instance_variable_set(:@borrowed, true)
388
+ obj
389
+ end
390
+
391
+ private
392
+
393
+ def register_finalizer
394
+ return if instance_variable_defined?(:@borrowed) && @borrowed
395
+
396
+ HarfBuzz::Font.define_finalizer(self, @ptr)
397
+ end
398
+
399
+ def self.define_finalizer(obj, ptr)
400
+ destroy = C.method(:hb_font_destroy)
401
+ ObjectSpace.define_finalizer(obj, proc { destroy.call(ptr) })
402
+ end
403
+ end
404
+ end
@@ -0,0 +1,286 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ # Wraps hb_font_funcs_t — customizable font callback table
5
+ #
6
+ # FontFuncs allows you to override how HarfBuzz queries glyph data from a font.
7
+ # This is used to implement custom font backends (e.g., connecting to FreeType,
8
+ # CoreText, or a custom renderer).
9
+ #
10
+ # @example Custom nominal glyph lookup
11
+ # funcs = HarfBuzz::FontFuncs.new
12
+ # funcs.on_nominal_glyph do |_font, codepoint|
13
+ # my_cmap[codepoint]
14
+ # end
15
+ # funcs.make_immutable!
16
+ # font.set_funcs(funcs)
17
+ class FontFuncs
18
+ attr_reader :ptr
19
+
20
+ def initialize
21
+ @ptr = C.hb_font_funcs_create
22
+ raise AllocationError, "Failed to create font funcs" if @ptr.null?
23
+
24
+ # Keep FFI::Function objects alive as long as this object is alive
25
+ @callbacks = {}
26
+ HarfBuzz::FontFuncs.define_finalizer(self, @ptr)
27
+ end
28
+
29
+ # Returns the singleton empty font funcs
30
+ # @return [FontFuncs]
31
+ def self.empty
32
+ obj = allocate
33
+ obj.instance_variable_set(:@ptr, C.hb_font_funcs_get_empty)
34
+ obj.instance_variable_set(:@borrowed, true)
35
+ obj.instance_variable_set(:@callbacks, {})
36
+ obj
37
+ end
38
+
39
+ # @return [Boolean] true if immutable
40
+ def immutable?
41
+ C.from_hb_bool(C.hb_font_funcs_is_immutable(@ptr))
42
+ end
43
+
44
+ # Makes the funcs table immutable (required before attaching to a font)
45
+ # @return [self]
46
+ def make_immutable!
47
+ C.hb_font_funcs_make_immutable(@ptr)
48
+ self
49
+ end
50
+
51
+ # Sets the horizontal font extents callback
52
+ # @yield [font] Called with the HarfBuzz font pointer
53
+ # @yieldreturn [Hash, nil] Hash with :ascender, :descender, :line_gap keys (in font units), or nil
54
+ def on_font_h_extents(&block)
55
+ cb = FFI::Function.new(:int, [:pointer, :pointer, :pointer, :pointer]) do |font_ptr, _font_data, extents_ptr, _user_data|
56
+ result = block.call(font_ptr)
57
+ next 0 unless result
58
+
59
+ extents = C::HbFontExtentsT.new(extents_ptr)
60
+ extents[:ascender] = result[:ascender] || 0
61
+ extents[:descender] = result[:descender] || 0
62
+ extents[:line_gap] = result[:line_gap] || 0
63
+ 1
64
+ end
65
+ @callbacks[:font_h_extents] = cb
66
+ C.hb_font_funcs_set_font_h_extents_func(@ptr, cb, nil, nil)
67
+ self
68
+ end
69
+
70
+ # Sets the vertical font extents callback
71
+ # @yield [font] Called with the HarfBuzz font pointer
72
+ # @yieldreturn [Hash, nil] Hash with :ascender, :descender, :line_gap keys, or nil
73
+ def on_font_v_extents(&block)
74
+ cb = FFI::Function.new(:int, [:pointer, :pointer, :pointer, :pointer]) do |font_ptr, _font_data, extents_ptr, _user_data|
75
+ result = block.call(font_ptr)
76
+ next 0 unless result
77
+
78
+ extents = C::HbFontExtentsT.new(extents_ptr)
79
+ extents[:ascender] = result[:ascender] || 0
80
+ extents[:descender] = result[:descender] || 0
81
+ extents[:line_gap] = result[:line_gap] || 0
82
+ 1
83
+ end
84
+ @callbacks[:font_v_extents] = cb
85
+ C.hb_font_funcs_set_font_v_extents_func(@ptr, cb, nil, nil)
86
+ self
87
+ end
88
+
89
+ # Sets the nominal glyph lookup callback
90
+ # @yield [font, codepoint] Called with font pointer and Unicode codepoint
91
+ # @yieldreturn [Integer, nil] Glyph ID or nil if not found
92
+ def on_nominal_glyph(&block)
93
+ cb = FFI::Function.new(:int, [:pointer, :pointer, :uint32, :pointer, :pointer]) do |font_ptr, _font_data, codepoint, glyph_out, _user_data|
94
+ glyph_id = block.call(font_ptr, codepoint)
95
+ next 0 unless glyph_id
96
+
97
+ glyph_out.write_uint32(glyph_id)
98
+ 1
99
+ end
100
+ @callbacks[:nominal_glyph] = cb
101
+ C.hb_font_funcs_set_nominal_glyph_func(@ptr, cb, nil, nil)
102
+ self
103
+ end
104
+
105
+ # Sets the variation glyph lookup callback
106
+ # @yield [font, codepoint, variation_selector] Called with font pointer, codepoint, and selector
107
+ # @yieldreturn [Integer, nil] Glyph ID or nil if not found
108
+ def on_variation_glyph(&block)
109
+ cb = FFI::Function.new(:int, [:pointer, :pointer, :uint32, :uint32, :pointer, :pointer]) do |font_ptr, _font_data, codepoint, selector, glyph_out, _user_data|
110
+ glyph_id = block.call(font_ptr, codepoint, selector)
111
+ next 0 unless glyph_id
112
+
113
+ glyph_out.write_uint32(glyph_id)
114
+ 1
115
+ end
116
+ @callbacks[:variation_glyph] = cb
117
+ C.hb_font_funcs_set_variation_glyph_func(@ptr, cb, nil, nil)
118
+ self
119
+ end
120
+
121
+ # Sets the horizontal glyph advance callback
122
+ # @yield [font, glyph] Called with font pointer and glyph ID
123
+ # @yieldreturn [Integer] Horizontal advance in font units
124
+ def on_glyph_h_advance(&block)
125
+ cb = FFI::Function.new(:int32, [:pointer, :pointer, :uint32, :pointer]) do |font_ptr, _font_data, glyph, _user_data|
126
+ block.call(font_ptr, glyph).to_i
127
+ end
128
+ @callbacks[:glyph_h_advance] = cb
129
+ C.hb_font_funcs_set_glyph_h_advance_func(@ptr, cb, nil, nil)
130
+ self
131
+ end
132
+
133
+ # Sets the vertical glyph advance callback
134
+ # @yield [font, glyph] Called with font pointer and glyph ID
135
+ # @yieldreturn [Integer] Vertical advance in font units
136
+ def on_glyph_v_advance(&block)
137
+ cb = FFI::Function.new(:int32, [:pointer, :pointer, :uint32, :pointer]) do |font_ptr, _font_data, glyph, _user_data|
138
+ block.call(font_ptr, glyph).to_i
139
+ end
140
+ @callbacks[:glyph_v_advance] = cb
141
+ C.hb_font_funcs_set_glyph_v_advance_func(@ptr, cb, nil, nil)
142
+ self
143
+ end
144
+
145
+ # Sets the horizontal glyph origin callback
146
+ # @yield [font, glyph] Called with font pointer and glyph ID
147
+ # @yieldreturn [Array<Integer>, nil] [x, y] origin or nil
148
+ def on_glyph_h_origin(&block)
149
+ cb = FFI::Function.new(:int, [:pointer, :pointer, :uint32, :pointer, :pointer, :pointer]) do |font_ptr, _font_data, glyph, x_out, y_out, _user_data|
150
+ result = block.call(font_ptr, glyph)
151
+ next 0 unless result
152
+
153
+ x_out.write_int32(result[0])
154
+ y_out.write_int32(result[1])
155
+ 1
156
+ end
157
+ @callbacks[:glyph_h_origin] = cb
158
+ C.hb_font_funcs_set_glyph_h_origin_func(@ptr, cb, nil, nil)
159
+ self
160
+ end
161
+
162
+ # Sets the vertical glyph origin callback
163
+ # @yield [font, glyph] Called with font pointer and glyph ID
164
+ # @yieldreturn [Array<Integer>, nil] [x, y] origin or nil
165
+ def on_glyph_v_origin(&block)
166
+ cb = FFI::Function.new(:int, [:pointer, :pointer, :uint32, :pointer, :pointer, :pointer]) do |font_ptr, _font_data, glyph, x_out, y_out, _user_data|
167
+ result = block.call(font_ptr, glyph)
168
+ next 0 unless result
169
+
170
+ x_out.write_int32(result[0])
171
+ y_out.write_int32(result[1])
172
+ 1
173
+ end
174
+ @callbacks[:glyph_v_origin] = cb
175
+ C.hb_font_funcs_set_glyph_v_origin_func(@ptr, cb, nil, nil)
176
+ self
177
+ end
178
+
179
+ # Sets the horizontal glyph kerning callback
180
+ # @yield [font, left_glyph, right_glyph] Called with font pointer and two glyph IDs
181
+ # @yieldreturn [Integer] Kerning value in font units
182
+ def on_glyph_h_kerning(&block)
183
+ cb = FFI::Function.new(:int32, [:pointer, :pointer, :uint32, :uint32, :pointer]) do |font_ptr, _font_data, left, right, _user_data|
184
+ block.call(font_ptr, left, right).to_i
185
+ end
186
+ @callbacks[:glyph_h_kerning] = cb
187
+ C.hb_font_funcs_set_glyph_h_kerning_func(@ptr, cb, nil, nil)
188
+ self
189
+ end
190
+
191
+ # Sets the glyph extents callback
192
+ # @yield [font, glyph] Called with font pointer and glyph ID
193
+ # @yieldreturn [Hash, nil] Hash with :x_bearing, :y_bearing, :width, :height keys, or nil
194
+ def on_glyph_extents(&block)
195
+ cb = FFI::Function.new(:int, [:pointer, :pointer, :uint32, :pointer, :pointer]) do |font_ptr, _font_data, glyph, extents_ptr, _user_data|
196
+ result = block.call(font_ptr, glyph)
197
+ next 0 unless result
198
+
199
+ extents = C::HbGlyphExtentsT.new(extents_ptr)
200
+ extents[:x_bearing] = result[:x_bearing] || 0
201
+ extents[:y_bearing] = result[:y_bearing] || 0
202
+ extents[:width] = result[:width] || 0
203
+ extents[:height] = result[:height] || 0
204
+ 1
205
+ end
206
+ @callbacks[:glyph_extents] = cb
207
+ C.hb_font_funcs_set_glyph_extents_func(@ptr, cb, nil, nil)
208
+ self
209
+ end
210
+
211
+ # Sets the glyph name callback
212
+ # @yield [font, glyph] Called with font pointer and glyph ID
213
+ # @yieldreturn [String, nil] Glyph name or nil
214
+ def on_glyph_name(&block)
215
+ cb = FFI::Function.new(:int, [:pointer, :pointer, :uint32, :pointer, :uint, :pointer]) do |font_ptr, _font_data, glyph, name_buf, name_buf_size, _user_data|
216
+ name = block.call(font_ptr, glyph)
217
+ next 0 unless name
218
+
219
+ bytes = name.bytesize
220
+ size = [bytes, name_buf_size - 1].min
221
+ name_buf.put_bytes(0, name, 0, size)
222
+ name_buf.put_uint8(size, 0) # null terminate
223
+ 1
224
+ end
225
+ @callbacks[:glyph_name] = cb
226
+ C.hb_font_funcs_set_glyph_name_func(@ptr, cb, nil, nil)
227
+ self
228
+ end
229
+
230
+ # Sets the glyph-from-name lookup callback
231
+ # @yield [font, name] Called with font pointer and glyph name string
232
+ # @yieldreturn [Integer, nil] Glyph ID or nil if not found
233
+ def on_glyph_from_name(&block)
234
+ cb = FFI::Function.new(:int, [:pointer, :pointer, :pointer, :int, :pointer, :pointer]) do |font_ptr, _font_data, name_ptr, name_len, glyph_out, _user_data|
235
+ name = name_len < 0 ? name_ptr.read_string : name_ptr.read_bytes(name_len)
236
+ glyph_id = block.call(font_ptr, name)
237
+ next 0 unless glyph_id
238
+
239
+ glyph_out.write_uint32(glyph_id)
240
+ 1
241
+ end
242
+ @callbacks[:glyph_from_name] = cb
243
+ C.hb_font_funcs_set_glyph_from_name_func(@ptr, cb, nil, nil)
244
+ self
245
+ end
246
+
247
+ # Sets the glyph contour point callback
248
+ # @yield [font, glyph, point_index] Called with font pointer, glyph ID, and point index
249
+ # @yieldreturn [Array<Integer>, nil] [x, y] contour point coordinates, or nil if not found
250
+ def on_glyph_contour_point(&block)
251
+ cb = FFI::Function.new(:int, [:pointer, :pointer, :uint32, :uint, :pointer, :pointer, :pointer]) do |font_ptr, _font_data, glyph, point_index, x_out, y_out, _user_data|
252
+ result = block.call(font_ptr, glyph, point_index)
253
+ next 0 unless result
254
+
255
+ x_out.write_int32(result[0])
256
+ y_out.write_int32(result[1])
257
+ 1
258
+ end
259
+ @callbacks[:glyph_contour_point] = cb
260
+ C.hb_font_funcs_set_glyph_contour_point_func(@ptr, cb, nil, nil)
261
+ self
262
+ end
263
+
264
+ # Sets the draw glyph callback (called when font draws a glyph via draw funcs)
265
+ # @yield [font, glyph, draw_funcs_ptr, draw_data_ptr] Called with font pointer, glyph ID, and draw state
266
+ def on_draw_glyph(&block)
267
+ cb = FFI::Function.new(:void, [:pointer, :pointer, :uint32, :pointer, :pointer, :pointer]) do |font_ptr, _font_data, glyph, draw_funcs_ptr, draw_data_ptr, _user_data|
268
+ block.call(font_ptr, glyph, draw_funcs_ptr, draw_data_ptr)
269
+ end
270
+ @callbacks[:draw_glyph] = cb
271
+ C.hb_font_funcs_set_draw_glyph_func(@ptr, cb, nil, nil)
272
+ self
273
+ end
274
+
275
+ def inspect
276
+ "#<HarfBuzz::FontFuncs immutable=#{immutable?}>"
277
+ end
278
+
279
+ def self.define_finalizer(obj, ptr)
280
+ return if obj.instance_variable_defined?(:@borrowed) && obj.instance_variable_get(:@borrowed)
281
+
282
+ destroy = C.method(:hb_font_funcs_destroy)
283
+ ObjectSpace.define_finalizer(obj, proc { destroy.call(ptr) })
284
+ end
285
+ end
286
+ end