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,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ # Wraps hb_face_t — a font face (font file + index)
5
+ class Face
6
+ attr_reader :ptr
7
+
8
+ # Creates a Face from a Blob and face index
9
+ # @param blob [Blob] Font data blob
10
+ # @param index [Integer] Face index within the blob (0 for single-face fonts)
11
+ def initialize(blob, index = 0)
12
+ @blob = blob
13
+ @ptr = C.hb_face_create(blob.ptr, index)
14
+ raise AllocationError, "Failed to create face" if @ptr.null?
15
+
16
+ register_finalizer
17
+ end
18
+
19
+ # Creates a Face with custom table access (callback-based)
20
+ # @yield [tag] Called with the hb_tag_t for each table requested
21
+ # @yieldreturn [Blob] Table blob
22
+ # @return [Face]
23
+ def self.for_tables(&block)
24
+ callback_holder = []
25
+ cb = FFI::Function.new(:pointer, [:pointer, :uint32, :pointer]) do |_face, tag, _user_data|
26
+ blob = block.call(tag)
27
+ blob ? blob.ptr : C.hb_blob_get_empty
28
+ end
29
+ callback_holder << cb
30
+
31
+ ptr = C.hb_face_create_for_tables(cb, nil, nil)
32
+ obj = allocate
33
+ obj.instance_variable_set(:@ptr, ptr)
34
+ obj.instance_variable_set(:@callback_holder, callback_holder)
35
+ obj.send(:register_finalizer)
36
+ obj
37
+ end
38
+
39
+ # Returns the singleton empty face
40
+ # @return [Face]
41
+ def self.empty
42
+ wrap_borrowed(C.hb_face_get_empty)
43
+ end
44
+
45
+ # Returns the number of faces in a blob
46
+ # @param blob [Blob] Font blob
47
+ # @return [Integer] Face count
48
+ def self.count(blob)
49
+ C.hb_face_count(blob.ptr)
50
+ end
51
+
52
+ # @return [Integer] Face index
53
+ def index
54
+ C.hb_face_get_index(@ptr)
55
+ end
56
+
57
+ # @param idx [Integer] Face index
58
+ def index=(idx)
59
+ C.hb_face_set_index(@ptr, idx)
60
+ end
61
+
62
+ # @return [Integer] Units per em
63
+ def upem
64
+ C.hb_face_get_upem(@ptr)
65
+ end
66
+
67
+ # @param u [Integer] Units per em
68
+ def upem=(u)
69
+ C.hb_face_set_upem(@ptr, u)
70
+ end
71
+
72
+ # @return [Integer] Number of glyphs in this face
73
+ def glyph_count
74
+ C.hb_face_get_glyph_count(@ptr)
75
+ end
76
+
77
+ # @param count [Integer] Glyph count override
78
+ def glyph_count=(count)
79
+ C.hb_face_set_glyph_count(@ptr, count)
80
+ end
81
+
82
+ # @return [Array<Integer>] Array of table tags
83
+ def table_tags
84
+ # First call: pass count=0 to get total number of tables (return value)
85
+ count_ptr = FFI::MemoryPointer.new(:uint)
86
+ count_ptr.write_uint(0)
87
+ total = C.hb_face_get_table_tags(@ptr, 0, count_ptr, nil)
88
+ return [] if total.zero?
89
+
90
+ tags_ptr = FFI::MemoryPointer.new(:uint32, total)
91
+ count_ptr.write_uint(total)
92
+ C.hb_face_get_table_tags(@ptr, 0, count_ptr, tags_ptr)
93
+ actual = count_ptr.read_uint
94
+ tags_ptr.read_array_of_uint32(actual)
95
+ end
96
+
97
+ # Returns the blob for a specific table
98
+ # @param tag [Integer] OpenType table tag
99
+ # @return [Blob] Table blob (owned)
100
+ def table(tag)
101
+ ptr = C.hb_face_reference_table(@ptr, tag)
102
+ Blob.wrap_owned(ptr)
103
+ end
104
+
105
+ alias reference_table table
106
+
107
+ # Returns the blob for the entire font face
108
+ # @return [Blob] Face blob (owned)
109
+ def blob
110
+ ptr = C.hb_face_reference_blob(@ptr)
111
+ Blob.wrap_owned(ptr)
112
+ end
113
+
114
+ # @return [Set] Set of all Unicode codepoints in this face
115
+ def unicodes
116
+ set = Set.new
117
+ C.hb_face_collect_unicodes(@ptr, set.ptr)
118
+ set
119
+ end
120
+
121
+ # @return [Map] Map from Unicode codepoints to nominal glyph IDs
122
+ def nominal_glyph_mapping
123
+ map = Map.new
124
+ C.hb_face_collect_nominal_glyph_mapping(@ptr, map.ptr, FFI::Pointer::NULL)
125
+ map
126
+ end
127
+
128
+ # @return [Set] Set of variation selector codepoints
129
+ def variation_selectors
130
+ set = Set.new
131
+ C.hb_face_collect_variation_selectors(@ptr, set.ptr)
132
+ set
133
+ end
134
+
135
+ # @param selector [Integer] Variation selector codepoint
136
+ # @return [Set] Set of Unicode codepoints supported via the variation selector
137
+ def variation_unicodes(selector)
138
+ set = Set.new
139
+ C.hb_face_collect_variation_unicodes(@ptr, selector, set.ptr)
140
+ set
141
+ end
142
+
143
+ # @return [Boolean] true if face is immutable
144
+ def immutable?
145
+ C.from_hb_bool(C.hb_face_is_immutable(@ptr))
146
+ end
147
+
148
+ # Makes the face immutable (thread-safe to share after this)
149
+ # @return [self]
150
+ def make_immutable!
151
+ C.hb_face_make_immutable(@ptr)
152
+ self
153
+ end
154
+
155
+ def inspect
156
+ "#<HarfBuzz::Face index=#{index} upem=#{upem} glyph_count=#{glyph_count}>"
157
+ end
158
+
159
+ def self.wrap_owned(ptr)
160
+ obj = allocate
161
+ obj.instance_variable_set(:@ptr, ptr)
162
+ obj.send(:register_finalizer)
163
+ obj
164
+ end
165
+
166
+ def self.wrap_borrowed(ptr)
167
+ obj = allocate
168
+ obj.instance_variable_set(:@ptr, ptr)
169
+ obj.instance_variable_set(:@borrowed, true)
170
+ obj
171
+ end
172
+
173
+ private
174
+
175
+ def register_finalizer
176
+ return if instance_variable_defined?(:@borrowed) && @borrowed
177
+
178
+ HarfBuzz::Face.define_finalizer(self, @ptr)
179
+ end
180
+
181
+ def self.define_finalizer(obj, ptr)
182
+ destroy = C.method(:hb_face_destroy)
183
+ ObjectSpace.define_finalizer(obj, proc { destroy.call(ptr) })
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ # Represents an OpenType feature (e.g., "liga", "+kern", "smcp=1")
5
+ class Feature
6
+ HB_FEATURE_GLOBAL_START = 0
7
+ HB_FEATURE_GLOBAL_END = 0xFFFFFFFF
8
+
9
+ def initialize(struct)
10
+ @struct = struct
11
+ end
12
+
13
+ # @return [Integer] Feature tag as uint32
14
+ def tag
15
+ @struct[:tag]
16
+ end
17
+
18
+ # @return [Integer] Feature value
19
+ def value
20
+ @struct[:value]
21
+ end
22
+
23
+ # @return [Integer] Start index in buffer (HB_FEATURE_GLOBAL_START for global)
24
+ def start
25
+ @struct[:start]
26
+ end
27
+
28
+ # @return [Integer] End index in buffer (HB_FEATURE_GLOBAL_END for global)
29
+ def end_index
30
+ @struct[:end]
31
+ end
32
+
33
+ # Parses a feature string such as "liga", "+kern", "-calt", "smcp=1"
34
+ # @param str [String] Feature string
35
+ # @return [Feature] Parsed feature
36
+ # @raise [FeatureParseError] If the string cannot be parsed
37
+ def self.from_string(str)
38
+ struct = C::HbFeatureT.new
39
+ result = C.hb_feature_from_string(str, str.bytesize, struct)
40
+ raise FeatureParseError, "Invalid feature string: #{str.inspect}" if result.zero?
41
+
42
+ new(struct)
43
+ end
44
+
45
+ # Builds a list of Feature objects from a Hash
46
+ # @param hash [Hash] Map of tag => value (true/false/Integer)
47
+ # @return [Array<Feature>] List of features
48
+ def self.from_hash(hash)
49
+ hash.map do |tag, value|
50
+ case value
51
+ when true then from_string("+#{tag}")
52
+ when false then from_string("-#{tag}")
53
+ when Integer then from_string("#{tag}=#{value}")
54
+ else raise InvalidArgumentError, "Unknown feature value: #{value.inspect}"
55
+ end
56
+ end
57
+ end
58
+
59
+ # Returns the FFI struct for passing to C functions
60
+ # @return [C::HbFeatureT]
61
+ def to_struct
62
+ @struct
63
+ end
64
+
65
+ # @return [String] Feature string representation
66
+ def to_s
67
+ buf = FFI::MemoryPointer.new(:char, 128)
68
+ C.hb_feature_to_string(@struct, buf, 128)
69
+ buf.read_string
70
+ end
71
+
72
+ def inspect
73
+ "#<HarfBuzz::Feature #{to_s}>"
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ # Utility module for converting between symbol arrays and bitwise flag integers
5
+ #
6
+ # @example Buffer flags
7
+ # flags = HarfBuzz::Flags.to_int(:buffer_flags, [:bot, :eot])
8
+ # buffer.flags = flags
9
+ #
10
+ # @example Subset flags
11
+ # flags = HarfBuzz::Flags.to_int(:subset_flags, [:no_hinting, :retain_gids])
12
+ # input.flags = flags
13
+ module Flags
14
+ # Maps flag set names to their symbol→integer mappings
15
+ MAPPINGS = {
16
+ buffer_flags: {
17
+ default: C::BUFFER_FLAG_DEFAULT,
18
+ bot: C::BUFFER_FLAG_BOT,
19
+ eot: C::BUFFER_FLAG_EOT,
20
+ preserve_default_ignorables: C::BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES,
21
+ remove_default_ignorables: C::BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES,
22
+ do_not_insert_dotted_circle: C::BUFFER_FLAG_DO_NOT_INSERT_DOTTED_CIRCLE,
23
+ verify: C::BUFFER_FLAG_VERIFY,
24
+ produce_unsafe_to_concat: C::BUFFER_FLAG_PRODUCE_UNSAFE_TO_CONCAT,
25
+ produce_safe_to_insert_tatweel: C::BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL
26
+ }.freeze,
27
+ glyph_flags: {
28
+ unsafe_to_break: C::GLYPH_FLAG_UNSAFE_TO_BREAK,
29
+ unsafe_to_concat: C::GLYPH_FLAG_UNSAFE_TO_CONCAT,
30
+ safe_to_insert_tatweel: C::GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL,
31
+ defined: C::GLYPH_FLAG_DEFINED
32
+ }.freeze,
33
+ ot_var_axis_flags: {
34
+ hidden: C::OT_VAR_AXIS_FLAG_HIDDEN
35
+ }.freeze,
36
+ subset_flags: {
37
+ default: C::SUBSET_FLAGS_DEFAULT,
38
+ no_hinting: C::SUBSET_FLAGS_NO_HINTING,
39
+ retain_gids: C::SUBSET_FLAGS_RETAIN_GIDS,
40
+ desubroutinize: C::SUBSET_FLAGS_DESUBROUTINIZE,
41
+ name_legacy: C::SUBSET_FLAGS_NAME_LEGACY,
42
+ set_overlaps_flag: C::SUBSET_FLAGS_SET_OVERLAPS_FLAG,
43
+ passthrough_unrecognized: C::SUBSET_FLAGS_PASSTHROUGH_UNRECOGNIZED,
44
+ notdef_outline: C::SUBSET_FLAGS_NOTDEF_OUTLINE,
45
+ glyph_names: C::SUBSET_FLAGS_GLYPH_NAMES,
46
+ no_prune_unicode_ranges: C::SUBSET_FLAGS_NO_PRUNE_UNICODE_RANGES,
47
+ optimize_iup_deltas: C::SUBSET_FLAGS_OPTIMIZE_IUP_DELTAS
48
+ }.freeze
49
+ }.freeze
50
+
51
+ # Converts an array of symbols to a bitwise flag integer
52
+ # @param mapping_name [Symbol] Name of the flag mapping (e.g., :buffer_flags)
53
+ # @param symbols [Array<Symbol>] Flag symbols to combine
54
+ # @return [Integer] Combined bitmask
55
+ # @raise [ArgumentError] If mapping_name or a symbol is unknown
56
+ def self.to_int(mapping_name, symbols)
57
+ mapping = MAPPINGS.fetch(mapping_name) do
58
+ raise ArgumentError, "Unknown flag mapping: #{mapping_name.inspect}. " \
59
+ "Available: #{MAPPINGS.keys.inspect}"
60
+ end
61
+
62
+ Array(symbols).inject(0) do |acc, sym|
63
+ bit = mapping.fetch(sym) do
64
+ raise ArgumentError, "Unknown flag #{sym.inspect} in #{mapping_name}. " \
65
+ "Available: #{mapping.keys.inspect}"
66
+ end
67
+ acc | bit
68
+ end
69
+ end
70
+
71
+ # Converts a bitwise flag integer to an array of symbols
72
+ # @param mapping_name [Symbol] Name of the flag mapping
73
+ # @param int_value [Integer] Bitmask to decode
74
+ # @return [Array<Symbol>] Symbols whose bits are set
75
+ # @raise [ArgumentError] If mapping_name is unknown
76
+ def self.to_symbols(mapping_name, int_value)
77
+ mapping = MAPPINGS.fetch(mapping_name) do
78
+ raise ArgumentError, "Unknown flag mapping: #{mapping_name.inspect}. " \
79
+ "Available: #{MAPPINGS.keys.inspect}"
80
+ end
81
+
82
+ mapping.filter_map { |sym, bit| sym if bit != 0 && (int_value & bit) == bit }
83
+ end
84
+ end
85
+ end