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,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ module OT
5
+ # OpenType Meta table API
6
+ module Meta
7
+ module_function
8
+
9
+ # Returns the tags of all meta table entries
10
+ # @param face [Face] Font face
11
+ # @return [Array<Integer>] Entry tags
12
+ def entry_tags(face)
13
+ count_ptr = FFI::MemoryPointer.new(:uint)
14
+ count_ptr.write_uint(0)
15
+ C.hb_ot_meta_get_entry_tags(face.ptr, 0, count_ptr, nil)
16
+ count = count_ptr.read_uint
17
+ return [] if count.zero?
18
+
19
+ tags_ptr = FFI::MemoryPointer.new(:uint32, count)
20
+ count_ptr.write_uint(count)
21
+ C.hb_ot_meta_get_entry_tags(face.ptr, 0, count_ptr, tags_ptr)
22
+ tags_ptr.read_array_of_uint32(count_ptr.read_uint)
23
+ end
24
+
25
+ # Returns the blob for a meta entry
26
+ # @param face [Face] Font face
27
+ # @param tag [Symbol] Entry tag (:design_languages or :supported_languages)
28
+ # @return [Blob] Entry data blob
29
+ def entry(face, tag)
30
+ Blob.wrap_owned(C.hb_ot_meta_reference_entry(face.ptr, tag))
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ module OT
5
+ # OpenType Metrics API
6
+ module Metrics
7
+ module_function
8
+
9
+ # Returns a metric position value (returns nil if not available)
10
+ # @param font [Font] Sized font
11
+ # @param tag [Integer] Metric tag
12
+ # @return [Integer, nil] Position or nil
13
+ def position(font, tag)
14
+ pos_ptr = FFI::MemoryPointer.new(:int32)
15
+ ok = C.hb_ot_metrics_get_position(font.ptr, tag, pos_ptr)
16
+ ok.zero? ? nil : pos_ptr.read_int32
17
+ end
18
+
19
+ # Returns a metric position with fallback (always returns a value)
20
+ # @param font [Font] Sized font
21
+ # @param tag [Integer] Metric tag
22
+ # @return [Integer] Position
23
+ def position_with_fallback(font, tag)
24
+ pos_ptr = FFI::MemoryPointer.new(:int32)
25
+ C.hb_ot_metrics_get_position_with_fallback(font.ptr, tag, pos_ptr)
26
+ pos_ptr.read_int32
27
+ end
28
+
29
+ # Returns a metric float variation value
30
+ # @param font [Font] Sized font
31
+ # @param tag [Integer] Metric tag
32
+ # @return [Float] Variation value
33
+ def variation(font, tag)
34
+ C.hb_ot_metrics_get_variation(font.ptr, tag)
35
+ end
36
+
37
+ # Returns the X variation for a metric
38
+ # @param font [Font] Sized font
39
+ # @param tag [Integer] Metric tag
40
+ # @return [Integer] X variation
41
+ def x_variation(font, tag)
42
+ C.hb_ot_metrics_get_x_variation(font.ptr, tag)
43
+ end
44
+
45
+ # Returns the Y variation for a metric
46
+ # @param font [Font] Sized font
47
+ # @param tag [Integer] Metric tag
48
+ # @return [Integer] Y variation
49
+ def y_variation(font, tag)
50
+ C.hb_ot_metrics_get_y_variation(font.ptr, tag)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ module OT
5
+ # OpenType Name table API
6
+ module Name
7
+ module_function
8
+
9
+ # Returns all name entries from the name table
10
+ # @param face [Face] Font face
11
+ # @return [Array<C::HbOtNameEntryT>] Name entries
12
+ def list(face)
13
+ count_ptr = FFI::MemoryPointer.new(:uint)
14
+ entries_ptr = C.hb_ot_name_list_names(face.ptr, count_ptr)
15
+ count = count_ptr.read_uint
16
+ return [] if count.zero? || entries_ptr.null?
17
+
18
+ count.times.map { |i| C::HbOtNameEntryT.new(entries_ptr + i * C::HbOtNameEntryT.size) }
19
+ end
20
+
21
+ # Returns a name as a UTF-8 string
22
+ # @param face [Face] Font face
23
+ # @param name_id [Integer] Name ID
24
+ # @param language [FFI::Pointer, nil] Language pointer (nil = default)
25
+ # @return [String, nil] Name string or nil if not found
26
+ def get_utf8(face, name_id, language = nil)
27
+ lang = language || C.hb_language_get_default
28
+ buf_size = FFI::MemoryPointer.new(:uint)
29
+ buf_size.write_uint(0)
30
+ C.hb_ot_name_get_utf8(face.ptr, name_id, lang, buf_size, nil)
31
+ size = buf_size.read_uint
32
+ return nil if size.zero?
33
+
34
+ buf = FFI::MemoryPointer.new(:char, size + 1)
35
+ buf_size.write_uint(size + 1)
36
+ C.hb_ot_name_get_utf8(face.ptr, name_id, lang, buf_size, buf)
37
+ buf.read_string(size)
38
+ end
39
+
40
+ # Returns a name as a UTF-16 encoded string
41
+ # @param face [Face] Font face
42
+ # @param name_id [Integer] Name ID
43
+ # @param language [FFI::Pointer, nil] Language pointer (nil = default)
44
+ # @return [String, nil] UTF-16LE encoded string or nil if not found
45
+ def get_utf16(face, name_id, language = nil)
46
+ lang = language || C.hb_language_get_default
47
+ buf_size = FFI::MemoryPointer.new(:uint)
48
+ buf_size.write_uint(0)
49
+ C.hb_ot_name_get_utf16(face.ptr, name_id, lang, buf_size, nil)
50
+ size = buf_size.read_uint
51
+ return nil if size.zero?
52
+
53
+ buf = FFI::MemoryPointer.new(:uint16, size + 1)
54
+ buf_size.write_uint(size + 1)
55
+ C.hb_ot_name_get_utf16(face.ptr, name_id, lang, buf_size, buf)
56
+ actual = buf_size.read_uint
57
+ buf.read_bytes(actual * 2).force_encoding("UTF-16LE")
58
+ end
59
+
60
+ # Returns a name as a UTF-32 encoded string
61
+ # @param face [Face] Font face
62
+ # @param name_id [Integer] Name ID
63
+ # @param language [FFI::Pointer, nil] Language pointer (nil = default)
64
+ # @return [String, nil] UTF-32LE encoded string or nil if not found
65
+ def get_utf32(face, name_id, language = nil)
66
+ lang = language || C.hb_language_get_default
67
+ buf_size = FFI::MemoryPointer.new(:uint)
68
+ buf_size.write_uint(0)
69
+ C.hb_ot_name_get_utf32(face.ptr, name_id, lang, buf_size, nil)
70
+ size = buf_size.read_uint
71
+ return nil if size.zero?
72
+
73
+ buf = FFI::MemoryPointer.new(:uint32, size + 1)
74
+ buf_size.write_uint(size + 1)
75
+ C.hb_ot_name_get_utf32(face.ptr, name_id, lang, buf_size, buf)
76
+ actual = buf_size.read_uint
77
+ buf.read_bytes(actual * 4).force_encoding("UTF-32LE")
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ module OT
5
+ # OpenType Shape query API
6
+ module Shape
7
+ module_function
8
+
9
+ # Returns the set of glyphs that could be produced by the given buffer
10
+ # @param font [Font] Sized font
11
+ # @param buffer [Buffer] Text buffer
12
+ # @param features [Array<Feature>] Features to apply
13
+ # @return [HarfBuzz::Set] Set of glyph IDs
14
+ def glyphs_closure(font, buffer, features = [])
15
+ set = HarfBuzz::Set.new
16
+ features_ptr = HarfBuzz.send(:build_features_ptr, features)
17
+ C.hb_ot_shape_glyphs_closure(
18
+ font.ptr, buffer.ptr, features_ptr, features.size, set.ptr
19
+ )
20
+ set
21
+ end
22
+
23
+ # Returns the lookup indices used by a shape plan for a given table
24
+ # @param plan [ShapePlan] Shape plan
25
+ # @param table_tag [Integer] GSUB or GPOS tag
26
+ # @return [HarfBuzz::Set] Set of lookup indices
27
+ def plan_collect_lookups(plan, table_tag)
28
+ set = HarfBuzz::Set.new
29
+ C.hb_ot_shape_plan_collect_lookups(plan.ptr, table_tag, set.ptr)
30
+ set
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ module OT
5
+ # OpenType Variable Fonts API
6
+ module Var
7
+ module_function
8
+
9
+ # @param face [Face] Font face
10
+ # @return [Boolean] true if the face has variation data
11
+ def has_data?(face)
12
+ C.from_hb_bool(C.hb_ot_var_has_data(face.ptr))
13
+ end
14
+
15
+ # @param face [Face] Font face
16
+ # @return [Integer] Number of variation axes
17
+ def axis_count(face)
18
+ C.hb_ot_var_get_axis_count(face.ptr)
19
+ end
20
+
21
+ # Returns information about all variation axes
22
+ # @param face [Face] Font face
23
+ # @return [Array<C::HbOtVarAxisInfoT>] Axis info array
24
+ def axis_infos(face)
25
+ count = axis_count(face)
26
+ return [] if count.zero?
27
+
28
+ count_ptr = FFI::MemoryPointer.new(:uint)
29
+ count_ptr.write_uint(count)
30
+ infos_ptr = FFI::MemoryPointer.new(C::HbOtVarAxisInfoT, count)
31
+ C.hb_ot_var_get_axis_infos(face.ptr, 0, count_ptr, infos_ptr)
32
+ actual = count_ptr.read_uint
33
+ actual.times.map { |i| C::HbOtVarAxisInfoT.new(infos_ptr + i * C::HbOtVarAxisInfoT.size) }
34
+ end
35
+
36
+ # Finds a specific axis by tag
37
+ # @param face [Face] Font face
38
+ # @param tag [Integer] Axis tag (e.g., HarfBuzz.tag("wght"))
39
+ # @return [C::HbOtVarAxisInfoT, nil] Axis info or nil
40
+ def find_axis_info(face, tag)
41
+ info = C::HbOtVarAxisInfoT.new
42
+ ok = C.hb_ot_var_find_axis_info(face.ptr, tag, info)
43
+ ok.zero? ? nil : info
44
+ end
45
+
46
+ # @param face [Face] Font face
47
+ # @return [Integer] Number of named instances
48
+ def named_instance_count(face)
49
+ C.hb_ot_var_get_named_instance_count(face.ptr)
50
+ end
51
+
52
+ # @param face [Face] Font face
53
+ # @param idx [Integer] Instance index
54
+ # @return [Integer] Name ID for the subfamily name
55
+ def named_instance_subfamily_name_id(face, idx)
56
+ C.hb_ot_var_named_instance_get_subfamily_name_id(face.ptr, idx)
57
+ end
58
+
59
+ # @param face [Face] Font face
60
+ # @param idx [Integer] Instance index
61
+ # @return [Integer] Name ID for the PostScript name
62
+ def named_instance_postscript_name_id(face, idx)
63
+ C.hb_ot_var_named_instance_get_postscript_name_id(face.ptr, idx)
64
+ end
65
+
66
+ # Returns design coordinates for a named instance
67
+ # @param face [Face] Font face
68
+ # @param idx [Integer] Instance index
69
+ # @return [Array<Float>] Design coordinates
70
+ def named_instance_design_coords(face, idx)
71
+ count = axis_count(face)
72
+ return [] if count.zero?
73
+
74
+ count_ptr = FFI::MemoryPointer.new(:uint)
75
+ count_ptr.write_uint(count)
76
+ coords_ptr = FFI::MemoryPointer.new(:float, count)
77
+ C.hb_ot_var_named_instance_get_design_coords(face.ptr, idx, count_ptr, coords_ptr)
78
+ coords_ptr.read_array_of_float(count_ptr.read_uint)
79
+ end
80
+
81
+ # Normalizes variation axis values
82
+ # @param face [Face] Font face
83
+ # @param variations [Array<Variation>] Design-space variation values
84
+ # @return [Array<Integer>] Normalized coordinates
85
+ def normalize_variations(face, variations)
86
+ count = C.hb_ot_var_get_axis_count(face.ptr)
87
+ return [] if count.zero?
88
+
89
+ var_structs = variations.map(&:to_struct)
90
+ var_ptr = FFI::MemoryPointer.new(C::HbVariationT, variations.size)
91
+ var_structs.each_with_index do |s, i|
92
+ var_ptr.put_bytes(i * C::HbVariationT.size,
93
+ s.to_ptr.read_bytes(C::HbVariationT.size))
94
+ end
95
+ coords_ptr = FFI::MemoryPointer.new(:int32, count)
96
+ C.hb_ot_var_normalize_variations(face.ptr, var_ptr, variations.size, coords_ptr, count)
97
+ coords_ptr.read_array_of_int32(count)
98
+ end
99
+
100
+ # Normalizes design-space coordinates to normalized space
101
+ # @param face [Face] Font face
102
+ # @param coords [Array<Float>] Design coordinates
103
+ # @return [Array<Integer>] Normalized coordinates
104
+ def normalize_coords(face, coords)
105
+ count = axis_count(face)
106
+ return [] if count.zero?
107
+
108
+ in_ptr = FFI::MemoryPointer.new(:float, coords.size)
109
+ in_ptr.put_array_of_float(0, coords)
110
+ out_ptr = FFI::MemoryPointer.new(:int32, count)
111
+ C.hb_ot_var_normalize_coords(face.ptr, coords.size, in_ptr, out_ptr)
112
+ out_ptr.read_array_of_int32(count)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ # Wraps hb_paint_funcs_t — callbacks for color glyph rendering (COLRv1)
5
+ class PaintFuncs
6
+ attr_reader :ptr
7
+
8
+ def initialize
9
+ @ptr = C.hb_paint_funcs_create
10
+ raise AllocationError, "Failed to create paint_funcs" if @ptr.null?
11
+
12
+ HarfBuzz::PaintFuncs.define_finalizer(self, @ptr)
13
+ end
14
+
15
+ # @return [Boolean] true if immutable
16
+ def immutable?
17
+ C.from_hb_bool(C.hb_paint_funcs_is_immutable(@ptr))
18
+ end
19
+
20
+ # Makes immutable
21
+ # @return [self]
22
+ def make_immutable!
23
+ C.hb_paint_funcs_make_immutable(@ptr)
24
+ self
25
+ end
26
+
27
+ # Sets push_transform callback
28
+ # @yield [xx, yx, xy, yy, dx, dy] 2D transform matrix components
29
+ def on_push_transform(&block)
30
+ @push_transform_callback = block
31
+ cb = FFI::Function.new(:void,
32
+ [:pointer, :pointer, :float, :float, :float, :float, :float, :float, :pointer]) do
33
+ |_pfuncs, _paint_data, xx, yx, xy, yy, dx, dy, _user_data|
34
+ block.call(xx, yx, xy, yy, dx, dy)
35
+ end
36
+ @push_transform_ffi = cb
37
+ C.hb_paint_funcs_set_push_transform_func(@ptr, cb, nil, nil)
38
+ end
39
+
40
+ # Sets pop_transform callback
41
+ # @yield Called when a transform is popped
42
+ def on_pop_transform(&block)
43
+ @pop_transform_callback = block
44
+ cb = FFI::Function.new(:void, [:pointer, :pointer, :pointer]) do
45
+ |_pfuncs, _paint_data, _user_data|
46
+ block.call
47
+ end
48
+ @pop_transform_ffi = cb
49
+ C.hb_paint_funcs_set_pop_transform_func(@ptr, cb, nil, nil)
50
+ end
51
+
52
+ # Sets push_clip_glyph callback
53
+ # @yield [glyph] Glyph ID used as clip mask
54
+ def on_push_clip_glyph(&block)
55
+ @push_clip_glyph_callback = block
56
+ cb = FFI::Function.new(:void, [:pointer, :pointer, :uint32, :pointer, :pointer]) do
57
+ |_pfuncs, _paint_data, glyph, _font, _user_data|
58
+ block.call(glyph)
59
+ end
60
+ @push_clip_glyph_ffi = cb
61
+ C.hb_paint_funcs_set_push_clip_glyph_func(@ptr, cb, nil, nil)
62
+ end
63
+
64
+ # Sets push_clip_rectangle callback
65
+ # @yield [xmin, ymin, xmax, ymax]
66
+ def on_push_clip_rectangle(&block)
67
+ @push_clip_rectangle_callback = block
68
+ cb = FFI::Function.new(:void,
69
+ [:pointer, :pointer, :float, :float, :float, :float, :pointer]) do
70
+ |_pfuncs, _paint_data, xmin, ymin, xmax, ymax, _user_data|
71
+ block.call(xmin, ymin, xmax, ymax)
72
+ end
73
+ @push_clip_rectangle_ffi = cb
74
+ C.hb_paint_funcs_set_push_clip_rectangle_func(@ptr, cb, nil, nil)
75
+ end
76
+
77
+ # Sets pop_clip callback
78
+ # @yield Called when clip is popped
79
+ def on_pop_clip(&block)
80
+ @pop_clip_callback = block
81
+ cb = FFI::Function.new(:void, [:pointer, :pointer, :pointer]) do
82
+ |_pfuncs, _paint_data, _user_data|
83
+ block.call
84
+ end
85
+ @pop_clip_ffi = cb
86
+ C.hb_paint_funcs_set_pop_clip_func(@ptr, cb, nil, nil)
87
+ end
88
+
89
+ # Sets color callback
90
+ # @yield [is_foreground, color] Color fill
91
+ def on_color(&block)
92
+ @color_callback = block
93
+ cb = FFI::Function.new(:void, [:pointer, :pointer, :int, :uint32, :pointer]) do
94
+ |_pfuncs, _paint_data, is_foreground, color, _user_data|
95
+ block.call(C.from_hb_bool(is_foreground), color)
96
+ end
97
+ @color_ffi = cb
98
+ C.hb_paint_funcs_set_color_func(@ptr, cb, nil, nil)
99
+ end
100
+
101
+ # Sets push_group callback
102
+ # @yield Called when a compositing group is started
103
+ def on_push_group(&block)
104
+ @push_group_callback = block
105
+ cb = FFI::Function.new(:void, [:pointer, :pointer, :pointer]) do
106
+ |_pfuncs, _paint_data, _user_data|
107
+ block.call
108
+ end
109
+ @push_group_ffi = cb
110
+ C.hb_paint_funcs_set_push_group_func(@ptr, cb, nil, nil)
111
+ end
112
+
113
+ # Sets pop_group callback
114
+ # @yield [mode] Composite mode
115
+ def on_pop_group(&block)
116
+ @pop_group_callback = block
117
+ cb = FFI::Function.new(:void, [:pointer, :pointer, :int, :pointer]) do
118
+ |_pfuncs, _paint_data, mode, _user_data|
119
+ block.call(mode)
120
+ end
121
+ @pop_group_ffi = cb
122
+ C.hb_paint_funcs_set_pop_group_func(@ptr, cb, nil, nil)
123
+ end
124
+
125
+ def inspect
126
+ "#<HarfBuzz::PaintFuncs immutable=#{immutable?}>"
127
+ end
128
+
129
+ def self.define_finalizer(obj, ptr)
130
+ destroy = C.method(:hb_paint_funcs_destroy)
131
+ ObjectSpace.define_finalizer(obj, proc { destroy.call(ptr) })
132
+ end
133
+ end
134
+ end