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,497 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ # Wraps hb_buffer_t — text buffer for shaping
5
+ class Buffer
6
+ attr_reader :ptr
7
+
8
+ # Creates a new empty buffer
9
+ def initialize
10
+ @ptr = C.hb_buffer_create
11
+ raise AllocationError, "Failed to create buffer" if @ptr.null?
12
+
13
+ register_finalizer
14
+ end
15
+
16
+ # Creates a buffer similar to this one (same properties, empty content)
17
+ # @return [Buffer]
18
+ def create_similar
19
+ ptr = C.hb_buffer_create_similar(@ptr)
20
+ self.class.wrap_owned(ptr)
21
+ end
22
+
23
+ # Returns the singleton empty buffer
24
+ # @return [Buffer]
25
+ def self.empty
26
+ wrap_borrowed(C.hb_buffer_get_empty)
27
+ end
28
+
29
+ # Resets all buffer state
30
+ # @return [self]
31
+ def reset
32
+ C.hb_buffer_reset(@ptr)
33
+ self
34
+ end
35
+
36
+ # Clears text content but keeps properties
37
+ # @return [self]
38
+ def clear
39
+ C.hb_buffer_clear_contents(@ptr)
40
+ self
41
+ end
42
+
43
+ # Pre-allocates buffer capacity
44
+ # @param size [Integer] Number of glyphs to pre-allocate
45
+ # @return [Boolean] Success
46
+ def pre_allocate(size)
47
+ C.from_hb_bool(C.hb_buffer_pre_allocate(@ptr, size))
48
+ end
49
+
50
+ # @return [Boolean] true if last allocation was successful
51
+ def allocation_successful?
52
+ C.from_hb_bool(C.hb_buffer_allocation_successful(@ptr))
53
+ end
54
+
55
+ # Adds a single codepoint with cluster index
56
+ # @param codepoint [Integer] Unicode codepoint
57
+ # @param cluster [Integer] Cluster index
58
+ # @return [self]
59
+ def add(codepoint, cluster)
60
+ C.hb_buffer_add(@ptr, codepoint, cluster)
61
+ self
62
+ end
63
+
64
+ # Adds UTF-8 text to the buffer
65
+ # @param text [String] Text to add (will be encoded as UTF-8)
66
+ # @param item_offset [Integer] Offset into text (for cluster calculation)
67
+ # @param item_length [Integer] Length (-1 = full text)
68
+ # @return [self]
69
+ def add_utf8(text, item_offset: 0, item_length: -1)
70
+ encoded = text.encode("UTF-8")
71
+ mem = FFI::MemoryPointer.from_string(encoded)
72
+ C.hb_buffer_add_utf8(@ptr, mem, encoded.bytesize, item_offset, item_length)
73
+ self
74
+ end
75
+
76
+ # Adds UTF-16 text to the buffer
77
+ # @param text [String] Text (will be encoded as UTF-16LE)
78
+ # @param item_offset [Integer] Offset in code units
79
+ # @param item_length [Integer] Length in code units (-1 = full text)
80
+ # @return [self]
81
+ def add_utf16(text, item_offset: 0, item_length: -1)
82
+ encoded = text.encode("UTF-16LE")
83
+ mem = FFI::MemoryPointer.new(:uint16, encoded.bytesize / 2 + 1)
84
+ mem.put_bytes(0, encoded)
85
+ C.hb_buffer_add_utf16(@ptr, mem, encoded.bytesize / 2, item_offset, item_length)
86
+ self
87
+ end
88
+
89
+ # Adds UTF-32 text to the buffer
90
+ # @param text [String] Text (will be encoded as UTF-32LE)
91
+ # @param item_offset [Integer] Offset in code units
92
+ # @param item_length [Integer] Length in code units (-1 = full text)
93
+ # @return [self]
94
+ def add_utf32(text, item_offset: 0, item_length: -1)
95
+ encoded = text.encode("UTF-32LE")
96
+ mem = FFI::MemoryPointer.new(:uint32, encoded.bytesize / 4 + 1)
97
+ mem.put_bytes(0, encoded)
98
+ C.hb_buffer_add_utf32(@ptr, mem, encoded.bytesize / 4, item_offset, item_length)
99
+ self
100
+ end
101
+
102
+ # Adds Latin-1 (ISO-8859-1) text to the buffer
103
+ # @param text [String] Text to add (will be encoded as ISO-8859-1)
104
+ # @param item_offset [Integer] Offset into text (for cluster calculation)
105
+ # @param item_length [Integer] Length (-1 = full text)
106
+ # @return [self]
107
+ def add_latin1(text, item_offset: 0, item_length: -1)
108
+ encoded = text.encode("ISO-8859-1")
109
+ mem = FFI::MemoryPointer.new(:uint8, encoded.bytesize)
110
+ mem.put_bytes(0, encoded)
111
+ C.hb_buffer_add_latin1(@ptr, mem, encoded.bytesize, item_offset, item_length)
112
+ self
113
+ end
114
+
115
+ # Adds an array of Unicode codepoints to the buffer
116
+ # @param codepoints [Array<Integer>] Array of Unicode codepoints
117
+ # @param item_offset [Integer] Offset
118
+ # @param item_length [Integer] Length (-1 = full array)
119
+ # @return [self]
120
+ def add_codepoints(codepoints, item_offset: 0, item_length: -1)
121
+ mem = FFI::MemoryPointer.new(:uint32, codepoints.size)
122
+ mem.put_array_of_uint32(0, codepoints)
123
+ C.hb_buffer_add_codepoints(@ptr, mem, codepoints.size, item_offset, item_length)
124
+ self
125
+ end
126
+
127
+ # Appends part of another buffer
128
+ # @param other [Buffer] Source buffer
129
+ # @param start [Integer] Start index
130
+ # @param end_ [Integer] End index
131
+ # @return [self]
132
+ def append(other, start, end_)
133
+ C.hb_buffer_append(@ptr, other.ptr, start, end_)
134
+ self
135
+ end
136
+
137
+ # @return [Symbol] Content type (:invalid, :unicode, :glyphs)
138
+ def content_type
139
+ C.hb_buffer_get_content_type(@ptr)
140
+ end
141
+
142
+ # @param type [Symbol] Content type
143
+ def content_type=(type)
144
+ C.hb_buffer_set_content_type(@ptr, type)
145
+ end
146
+
147
+ # @return [Symbol] Buffer direction (:ltr, :rtl, :ttb, :btt, :invalid)
148
+ def direction
149
+ C.hb_buffer_get_direction(@ptr)
150
+ end
151
+
152
+ # @param dir [Symbol] Direction (:ltr, :rtl, :ttb, :btt)
153
+ def direction=(dir)
154
+ C.hb_buffer_set_direction(@ptr, dir)
155
+ end
156
+
157
+ # @return [Integer] Script (hb_script_t value)
158
+ def script
159
+ C.hb_buffer_get_script(@ptr)
160
+ end
161
+
162
+ # @param script [Integer] Script value
163
+ def script=(script)
164
+ C.hb_buffer_set_script(@ptr, script)
165
+ end
166
+
167
+ # @return [FFI::Pointer] Language pointer
168
+ def language
169
+ C.hb_buffer_get_language(@ptr)
170
+ end
171
+
172
+ # @param lang [FFI::Pointer] Language pointer (use HarfBuzz.language("en"))
173
+ def language=(lang)
174
+ C.hb_buffer_set_language(@ptr, lang)
175
+ end
176
+
177
+ # @return [UnicodeFuncs] Unicode functions associated with this buffer
178
+ def unicode_funcs
179
+ ptr = C.hb_buffer_get_unicode_funcs(@ptr)
180
+ UnicodeFuncs.wrap_borrowed(ptr)
181
+ end
182
+
183
+ # @param ufuncs [UnicodeFuncs] Unicode functions to use for this buffer
184
+ def unicode_funcs=(ufuncs)
185
+ C.hb_buffer_set_unicode_funcs(@ptr, ufuncs.ptr)
186
+ end
187
+
188
+ # @return [C::HbSegmentPropertiesT] Segment properties struct
189
+ def segment_properties
190
+ props = C::HbSegmentPropertiesT.new
191
+ C.hb_buffer_get_segment_properties(@ptr, props.to_ptr)
192
+ props
193
+ end
194
+
195
+ # @param props [C::HbSegmentPropertiesT] Segment properties struct
196
+ def segment_properties=(props)
197
+ C.hb_buffer_set_segment_properties(@ptr, props.to_ptr)
198
+ end
199
+
200
+ # Guesses direction/script/language from buffer contents
201
+ # @return [self]
202
+ def guess_segment_properties
203
+ C.hb_buffer_guess_segment_properties(@ptr)
204
+ self
205
+ end
206
+
207
+ # @return [Integer] Buffer flags bitmask
208
+ def flags
209
+ C.hb_buffer_get_flags(@ptr)
210
+ end
211
+
212
+ # @param flags [Integer] Buffer flags bitmask
213
+ def flags=(flags)
214
+ C.hb_buffer_set_flags(@ptr, flags)
215
+ end
216
+
217
+ # @return [Symbol] Cluster level
218
+ def cluster_level
219
+ C.hb_buffer_get_cluster_level(@ptr)
220
+ end
221
+
222
+ # @param level [Symbol] Cluster level
223
+ def cluster_level=(level)
224
+ C.hb_buffer_set_cluster_level(@ptr, level)
225
+ end
226
+
227
+ # @return [Integer] Replacement codepoint for invalid input
228
+ def replacement_codepoint
229
+ C.hb_buffer_get_replacement_codepoint(@ptr)
230
+ end
231
+
232
+ # @param cp [Integer] Replacement codepoint
233
+ def replacement_codepoint=(cp)
234
+ C.hb_buffer_set_replacement_codepoint(@ptr, cp)
235
+ end
236
+
237
+ # @return [Integer] Invisible glyph ID
238
+ def invisible_glyph
239
+ C.hb_buffer_get_invisible_glyph(@ptr)
240
+ end
241
+
242
+ # @param glyph [Integer] Invisible glyph ID
243
+ def invisible_glyph=(glyph)
244
+ C.hb_buffer_set_invisible_glyph(@ptr, glyph)
245
+ end
246
+
247
+ # @return [Integer] Not-found glyph ID
248
+ def not_found_glyph
249
+ C.hb_buffer_get_not_found_glyph(@ptr)
250
+ end
251
+
252
+ # @param glyph [Integer] Not-found glyph ID
253
+ def not_found_glyph=(glyph)
254
+ C.hb_buffer_set_not_found_glyph(@ptr, glyph)
255
+ end
256
+
257
+ # @return [Integer] Random state
258
+ # @raise [NotImplementedError] if HarfBuzz < 7.2.0
259
+ def random_state
260
+ unless C.respond_to?(:hb_buffer_get_random_state)
261
+ raise NotImplementedError, "random_state requires HarfBuzz >= 7.2.0"
262
+ end
263
+
264
+ C.hb_buffer_get_random_state(@ptr)
265
+ end
266
+
267
+ # @param state [Integer] Random state
268
+ # @raise [NotImplementedError] if HarfBuzz < 7.2.0
269
+ def random_state=(state)
270
+ unless C.respond_to?(:hb_buffer_set_random_state)
271
+ raise NotImplementedError, "random_state= requires HarfBuzz >= 7.2.0"
272
+ end
273
+
274
+ C.hb_buffer_set_random_state(@ptr, state)
275
+ end
276
+
277
+ # @return [Integer] Number of items in buffer
278
+ def length
279
+ C.hb_buffer_get_length(@ptr)
280
+ end
281
+
282
+ alias size length
283
+
284
+ # @param len [Integer] New buffer length
285
+ def length=(len)
286
+ C.hb_buffer_set_length(@ptr, len)
287
+ end
288
+
289
+ # @return [Array<GlyphInfo>] Array of glyph info structs
290
+ def glyph_infos
291
+ length_ptr = FFI::MemoryPointer.new(:uint)
292
+ infos_ptr = C.hb_buffer_get_glyph_infos(@ptr, length_ptr)
293
+ count = length_ptr.read_uint
294
+ return [] if infos_ptr.null? || count.zero?
295
+
296
+ count.times.map do |i|
297
+ GlyphInfo.new(infos_ptr + (i * C::HbGlyphInfoT.size))
298
+ end
299
+ end
300
+
301
+ # @return [Array<GlyphPosition>] Array of glyph position structs
302
+ def glyph_positions
303
+ length_ptr = FFI::MemoryPointer.new(:uint)
304
+ positions_ptr = C.hb_buffer_get_glyph_positions(@ptr, length_ptr)
305
+ count = length_ptr.read_uint
306
+ return [] if positions_ptr.null? || count.zero?
307
+
308
+ count.times.map do |i|
309
+ GlyphPosition.new(positions_ptr + (i * C::HbGlyphPositionT.size))
310
+ end
311
+ end
312
+
313
+ # @return [Boolean] true if the buffer has glyph position data
314
+ def has_positions?
315
+ C.from_hb_bool(C.hb_buffer_has_positions(@ptr))
316
+ end
317
+
318
+ # Normalizes glyph clusters
319
+ # @return [self]
320
+ def normalize_glyphs
321
+ C.hb_buffer_normalize_glyphs(@ptr)
322
+ self
323
+ end
324
+
325
+ # Reverses buffer contents
326
+ # @return [self]
327
+ def reverse
328
+ C.hb_buffer_reverse(@ptr)
329
+ self
330
+ end
331
+
332
+ # Reverses a range of buffer contents
333
+ # @param start [Integer] Start index
334
+ # @param end_ [Integer] End index
335
+ # @return [self]
336
+ def reverse_range(start, end_)
337
+ C.hb_buffer_reverse_range(@ptr, start, end_)
338
+ self
339
+ end
340
+
341
+ # Reverses clusters
342
+ # @return [self]
343
+ def reverse_clusters
344
+ C.hb_buffer_reverse_clusters(@ptr)
345
+ self
346
+ end
347
+
348
+ # Sets a message callback for debugging/tracing shaping
349
+ # @yield [message] Called with a debug message string
350
+ # @return [self]
351
+ def on_message(&block)
352
+ @message_callback = block
353
+ cb = FFI::Function.new(:int, [:pointer, :pointer, :string, :pointer]) do
354
+ |_buf_ptr, _font_ptr, message, _user_data|
355
+ block.call(message)
356
+ 1
357
+ end
358
+ @message_ffi = cb
359
+ C.hb_buffer_set_message_func(@ptr, cb, nil, nil)
360
+ self
361
+ end
362
+
363
+ # Computes difference flags between this buffer and another
364
+ # @param other [Buffer] Buffer to compare against
365
+ # @param dottedcircle_glyph [Integer] Dotted circle glyph ID
366
+ # @param position_fuzz [Integer] Position comparison tolerance
367
+ # @return [Integer] Difference flags bitmask
368
+ def diff(other, dottedcircle_glyph, position_fuzz)
369
+ C.hb_buffer_diff(@ptr, other.ptr, dottedcircle_glyph, position_fuzz)
370
+ end
371
+
372
+ # Serializes buffer contents (glyphs or unicode) to a string representation
373
+ # @param font [Font, nil] Font used for glyph names (optional, used for glyphs content)
374
+ # @param format [Symbol] Serialize format (:text or :json), defaults to :text
375
+ # @param flags [Integer] Serialize flags (0 = default)
376
+ # @return [String] Serialized data
377
+ def serialize(font: nil, format: :text, flags: 0)
378
+ buf_size = 4096
379
+ buf = FFI::MemoryPointer.new(:char, buf_size)
380
+ written_ptr = FFI::MemoryPointer.new(:uint)
381
+ font_ptr = font ? font.ptr : FFI::Pointer::NULL
382
+ C.hb_buffer_serialize(@ptr, 0, length, buf, buf_size, written_ptr, font_ptr, format, flags)
383
+ buf.read_string(written_ptr.read_uint)
384
+ end
385
+
386
+ # Serializes shaped glyphs to a string representation
387
+ # @param font [Font, nil] Font used for glyph names (optional)
388
+ # @param format [Symbol] Serialize format (:text or :json), defaults to :text
389
+ # @param flags [Integer] Serialize flags (0 = default)
390
+ # @return [String] Serialized glyph data
391
+ def serialize_glyphs(font: nil, format: :text, flags: 0)
392
+ buf_size = 4096
393
+ buf = FFI::MemoryPointer.new(:char, buf_size)
394
+ written_ptr = FFI::MemoryPointer.new(:uint)
395
+ font_ptr = font ? font.ptr : FFI::Pointer::NULL
396
+ C.hb_buffer_serialize_glyphs(@ptr, 0, length, buf, buf_size, written_ptr, font_ptr, format, flags)
397
+ buf.read_string(written_ptr.read_uint)
398
+ end
399
+
400
+ # Serializes Unicode codepoints to a string representation
401
+ # @param format [Symbol] Serialize format (:text or :json), defaults to :text
402
+ # @param flags [Integer] Serialize flags (0 = default)
403
+ # @return [String] Serialized unicode data
404
+ def serialize_unicode(format: :text, flags: 0)
405
+ buf_size = 4096
406
+ buf = FFI::MemoryPointer.new(:char, buf_size)
407
+ written_ptr = FFI::MemoryPointer.new(:uint)
408
+ C.hb_buffer_serialize_unicode(@ptr, 0, length, buf, buf_size, written_ptr, format, flags)
409
+ buf.read_string(written_ptr.read_uint)
410
+ end
411
+
412
+ # Deserializes glyph data from a string into this buffer
413
+ # @param str [String] Serialized glyph data
414
+ # @param font [Font, nil] Font for glyph name resolution
415
+ # @param format [Symbol] Format (:text or :json), defaults to :text
416
+ # @return [Boolean] true if successful
417
+ def deserialize_glyphs(str, font: nil, format: :text)
418
+ font_ptr = font ? font.ptr : FFI::Pointer::NULL
419
+ end_ptr = FFI::MemoryPointer.new(:pointer)
420
+ C.from_hb_bool(
421
+ C.hb_buffer_deserialize_glyphs(@ptr, str, str.bytesize, end_ptr, font_ptr, format)
422
+ )
423
+ end
424
+
425
+ # Deserializes Unicode codepoints from a string into this buffer
426
+ # @param str [String] Serialized unicode data
427
+ # @param format [Symbol] Format (:text or :json), defaults to :text
428
+ # @return [Boolean] true if successful
429
+ def deserialize_unicode(str, format: :text)
430
+ end_ptr = FFI::MemoryPointer.new(:pointer)
431
+ C.from_hb_bool(
432
+ C.hb_buffer_deserialize_unicode(@ptr, str, str.bytesize, end_ptr, format)
433
+ )
434
+ end
435
+
436
+ def inspect
437
+ "#<HarfBuzz::Buffer length=#{length} direction=#{direction} content_type=#{content_type}>"
438
+ end
439
+
440
+ # Converts a format string to a serialize format symbol
441
+ # @param str [String] Format name (e.g., "text", "json")
442
+ # @return [Symbol] Format symbol
443
+ def self.serialize_format(str)
444
+ C.hb_buffer_serialize_format_from_string(str, str.bytesize)
445
+ end
446
+
447
+ # Converts a serialize format symbol to a string
448
+ # @param fmt [Symbol] Format symbol
449
+ # @return [String] Format name
450
+ def self.serialize_format_name(fmt)
451
+ C.hb_buffer_serialize_format_to_string(fmt)
452
+ end
453
+
454
+ # Returns a list of available serialize format names
455
+ # @return [Array<String>] Format names
456
+ def self.serialize_formats
457
+ ptr = C.hb_buffer_serialize_list_formats
458
+ result = []
459
+ i = 0
460
+ loop do
461
+ p = ptr.get_pointer(i * FFI::Pointer.size)
462
+ break if p.null?
463
+
464
+ result << p.read_string
465
+ i += 1
466
+ end
467
+ result
468
+ end
469
+
470
+ def self.wrap_owned(ptr)
471
+ obj = allocate
472
+ obj.instance_variable_set(:@ptr, ptr)
473
+ obj.send(:register_finalizer)
474
+ obj
475
+ end
476
+
477
+ def self.wrap_borrowed(ptr)
478
+ obj = allocate
479
+ obj.instance_variable_set(:@ptr, ptr)
480
+ obj.instance_variable_set(:@borrowed, true)
481
+ obj
482
+ end
483
+
484
+ private
485
+
486
+ def register_finalizer
487
+ return if instance_variable_defined?(:@borrowed) && @borrowed
488
+
489
+ HarfBuzz::Buffer.define_finalizer(self, @ptr)
490
+ end
491
+
492
+ def self.define_finalizer(obj, ptr)
493
+ destroy = C.method(:hb_buffer_destroy)
494
+ ObjectSpace.define_finalizer(obj, proc { destroy.call(ptr) })
495
+ end
496
+ end
497
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ module C
5
+ attach_function :hb_aat_layout_has_substitution, [:hb_face_t], :hb_bool_t
6
+ attach_function :hb_aat_layout_has_positioning, [:hb_face_t], :hb_bool_t
7
+ attach_function :hb_aat_layout_has_tracking, [:hb_face_t], :hb_bool_t
8
+ attach_function :hb_aat_layout_get_feature_types,
9
+ [:hb_face_t, :uint, :pointer, :pointer], :uint
10
+ attach_function :hb_aat_layout_feature_type_get_name_id,
11
+ [:hb_face_t, :uint, :pointer], :hb_bool_t
12
+ attach_function :hb_aat_layout_feature_type_get_selector_infos,
13
+ [:hb_face_t, :uint, :uint, :pointer, :pointer, :pointer], :uint
14
+ end
15
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ffi"
4
+
5
+ module HarfBuzz
6
+ # Low-level FFI bindings to HarfBuzz C API
7
+ module C
8
+ extend FFI::Library
9
+
10
+ # Load the HarfBuzz shared library
11
+ ffi_lib HarfBuzz.library_path
12
+
13
+ # === Basic Types ===
14
+ # hb_bool_t is int in C
15
+ typedef :int, :hb_bool_t
16
+
17
+ # Codepoint (Unicode code point)
18
+ typedef :uint32, :hb_codepoint_t
19
+
20
+ # Position value (signed 26.6 fixed point or font units)
21
+ typedef :int32, :hb_position_t
22
+
23
+ # Bitmask
24
+ typedef :uint32, :hb_mask_t
25
+
26
+ # OpenType tag (4-byte identifier)
27
+ typedef :uint32, :hb_tag_t
28
+
29
+ # Color value (RGBA)
30
+ typedef :uint32, :hb_color_t
31
+
32
+ # OpenType name ID
33
+ typedef :uint, :hb_ot_name_id_t
34
+
35
+ # === Opaque Pointer Types ===
36
+ # Each opaque pointer type is typedef'd for semantic clarity
37
+
38
+ # Blob (binary data)
39
+ typedef :pointer, :hb_blob_t
40
+
41
+ # Buffer (text buffer for shaping)
42
+ typedef :pointer, :hb_buffer_t
43
+
44
+ # Face (font face)
45
+ typedef :pointer, :hb_face_t
46
+
47
+ # Font (sized font instance)
48
+ typedef :pointer, :hb_font_t
49
+
50
+ # Font functions table
51
+ typedef :pointer, :hb_font_funcs_t
52
+
53
+ # Unicode functions table
54
+ typedef :pointer, :hb_unicode_funcs_t
55
+
56
+ # Draw functions table
57
+ typedef :pointer, :hb_draw_funcs_t
58
+
59
+ # Paint functions table
60
+ typedef :pointer, :hb_paint_funcs_t
61
+
62
+ # Map (integer to integer mapping)
63
+ typedef :pointer, :hb_map_t
64
+
65
+ # Set (integer set)
66
+ typedef :pointer, :hb_set_t
67
+
68
+ # Shape plan (cached shaping plan)
69
+ typedef :pointer, :hb_shape_plan_t
70
+
71
+ # Subset input
72
+ typedef :pointer, :hb_subset_input_t
73
+
74
+ # Subset plan
75
+ typedef :pointer, :hb_subset_plan_t
76
+
77
+ # === Helper Methods ===
78
+
79
+ # Converts Ruby boolean to hb_bool_t (int)
80
+ # @param value [Boolean] Ruby boolean
81
+ # @return [Integer] 1 for true, 0 for false
82
+ def self.to_hb_bool(value)
83
+ value ? 1 : 0
84
+ end
85
+
86
+ # Converts hb_bool_t (int) to Ruby boolean
87
+ # @param value [Integer] HarfBuzz boolean (0 or non-zero)
88
+ # @return [Boolean] Ruby boolean
89
+ def self.from_hb_bool(value)
90
+ !value.zero?
91
+ end
92
+
93
+ # Converts a 4-character tag string to hb_tag_t (uint32)
94
+ # @param tag [String] 4-character tag (e.g., "GSUB")
95
+ # @return [Integer] Tag as uint32
96
+ def self.tag_from_string(tag)
97
+ raise ArgumentError, "Tag must be a String" unless tag.is_a?(String)
98
+
99
+ # Pad or truncate to 4 bytes
100
+ bytes = tag.bytes.take(4)
101
+ bytes += [0x20] * (4 - bytes.size) if bytes.size < 4
102
+
103
+ # Pack as big-endian uint32
104
+ bytes.pack("C4").unpack1("N")
105
+ end
106
+
107
+ # Converts hb_tag_t (uint32) to 4-character string
108
+ # @param tag [Integer] Tag as uint32
109
+ # @return [String] 4-character tag string
110
+ def self.tag_to_string(tag)
111
+ [tag].pack("N").unpack("C4").pack("C*")
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ module C
5
+ attach_function :hb_blob_create,
6
+ [:pointer, :uint, :hb_memory_mode_t, :pointer, :pointer], :hb_blob_t
7
+ attach_function :hb_blob_create_from_file, [:string], :hb_blob_t
8
+ attach_function :hb_blob_create_from_file_or_fail, [:string], :hb_blob_t
9
+ attach_function :hb_blob_create_sub_blob, [:hb_blob_t, :uint, :uint], :hb_blob_t
10
+ attach_function :hb_blob_copy_writable_or_fail, [:hb_blob_t], :hb_blob_t
11
+ attach_function :hb_blob_get_empty, [], :hb_blob_t
12
+ attach_function :hb_blob_destroy, [:hb_blob_t], :void
13
+ attach_function :hb_blob_reference, [:hb_blob_t], :hb_blob_t
14
+ attach_function :hb_blob_get_length, [:hb_blob_t], :uint
15
+ attach_function :hb_blob_get_data, [:hb_blob_t, :pointer], :pointer
16
+ attach_function :hb_blob_get_data_writable, [:hb_blob_t, :pointer], :pointer
17
+ attach_function :hb_blob_is_immutable, [:hb_blob_t], :hb_bool_t
18
+ attach_function :hb_blob_make_immutable, [:hb_blob_t], :void
19
+ attach_function :hb_blob_set_user_data,
20
+ [:hb_blob_t, :pointer, :pointer, :pointer, :hb_bool_t], :hb_bool_t
21
+ attach_function :hb_blob_get_user_data, [:hb_blob_t, :pointer], :pointer
22
+ end
23
+ end