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,583 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ module OT
5
+ # OpenType Layout table queries (GSUB/GPOS)
6
+ module Layout
7
+ module_function
8
+
9
+ # @param face [Face] Font face
10
+ # @return [Boolean] true if the face has GDEF glyph classes
11
+ def has_glyph_classes?(face)
12
+ C.from_hb_bool(C.hb_ot_layout_has_glyph_classes(face.ptr))
13
+ end
14
+
15
+ # @param face [Face] Font face
16
+ # @return [Boolean] true if the face has a GSUB table
17
+ def has_substitution?(face)
18
+ C.from_hb_bool(C.hb_ot_layout_has_substitution(face.ptr))
19
+ end
20
+
21
+ # @param face [Face] Font face
22
+ # @return [Boolean] true if the face has a GPOS table
23
+ def has_positioning?(face)
24
+ C.from_hb_bool(C.hb_ot_layout_has_positioning(face.ptr))
25
+ end
26
+
27
+ # Returns the glyph class from GDEF
28
+ # @param face [Face] Font face
29
+ # @param glyph [Integer] Glyph ID
30
+ # @return [Symbol] Glyph class (:base_glyph, :ligature, :mark, :component)
31
+ def glyph_class(face, glyph)
32
+ C.hb_ot_layout_get_glyph_class(face.ptr, glyph)
33
+ end
34
+
35
+ # Returns all glyphs in a given GDEF class
36
+ # @param face [Face] Font face
37
+ # @param klass [Symbol] Glyph class
38
+ # @return [HarfBuzz::Set] Set of glyph IDs
39
+ def glyphs_in_class(face, klass)
40
+ set = HarfBuzz::Set.new
41
+ C.hb_ot_layout_get_glyphs_in_class(face.ptr, klass, set.ptr)
42
+ set
43
+ end
44
+
45
+ # Returns the script tags for a table
46
+ # @param face [Face] Font face
47
+ # @param table_tag [Integer] GSUB (0x47535542) or GPOS (0x47504F53)
48
+ # @return [Array<Integer>] Script tags
49
+ def script_tags(face, table_tag)
50
+ count_ptr = FFI::MemoryPointer.new(:uint)
51
+ count_ptr.write_uint(0)
52
+ C.hb_ot_layout_table_get_script_tags(face.ptr, table_tag, 0, count_ptr, nil)
53
+ count = count_ptr.read_uint
54
+ return [] if count.zero?
55
+
56
+ tags_ptr = FFI::MemoryPointer.new(:uint32, count)
57
+ count_ptr.write_uint(count)
58
+ C.hb_ot_layout_table_get_script_tags(face.ptr, table_tag, 0, count_ptr, tags_ptr)
59
+ tags_ptr.read_array_of_uint32(count_ptr.read_uint)
60
+ end
61
+
62
+ # Returns attachment point list for a glyph
63
+ # @param face [Face] Font face
64
+ # @param glyph [Integer] Glyph ID
65
+ # @return [Array<Integer>] Attachment point indices
66
+ def attach_points(face, glyph)
67
+ count_ptr = FFI::MemoryPointer.new(:uint)
68
+ count_ptr.write_uint(0)
69
+ total = C.hb_ot_layout_get_attach_points(face.ptr, glyph, 0, count_ptr, nil)
70
+ return [] if total.zero?
71
+
72
+ points_ptr = FFI::MemoryPointer.new(:uint, total)
73
+ count_ptr.write_uint(total)
74
+ C.hb_ot_layout_get_attach_points(face.ptr, glyph, 0, count_ptr, points_ptr)
75
+ points_ptr.read_array_of_uint(count_ptr.read_uint)
76
+ end
77
+
78
+ # Returns ligature caret positions for a glyph
79
+ # @param font [Font] Font
80
+ # @param dir [Symbol] Direction (:ltr, :rtl)
81
+ # @param glyph [Integer] Glyph ID
82
+ # @return [Array<Integer>] Caret positions
83
+ def ligature_carets(font, dir, glyph)
84
+ count_ptr = FFI::MemoryPointer.new(:uint)
85
+ count_ptr.write_uint(0)
86
+ total = C.hb_ot_layout_get_ligature_carets(font.ptr, dir, glyph, 0, count_ptr, nil)
87
+ return [] if total.zero?
88
+
89
+ carets_ptr = FFI::MemoryPointer.new(:int32, total)
90
+ count_ptr.write_uint(total)
91
+ C.hb_ot_layout_get_ligature_carets(font.ptr, dir, glyph, 0, count_ptr, carets_ptr)
92
+ carets_ptr.read_array_of_int32(count_ptr.read_uint)
93
+ end
94
+
95
+ # Finds a script in the table, returns [found, index]
96
+ # @param face [Face] Font face
97
+ # @param table_tag [Integer] GSUB or GPOS tag
98
+ # @param script_tag [Integer] Script tag to find
99
+ # @return [Array] [Boolean, Integer] found and script index
100
+ def find_script(face, table_tag, script_tag)
101
+ idx_ptr = FFI::MemoryPointer.new(:uint)
102
+ found = C.from_hb_bool(
103
+ C.hb_ot_layout_table_find_script(face.ptr, table_tag, script_tag, idx_ptr)
104
+ )
105
+ [found, idx_ptr.read_uint]
106
+ end
107
+
108
+ # Selects the best matching script from a list
109
+ # @param face [Face] Font face
110
+ # @param table_tag [Integer] GSUB or GPOS tag
111
+ # @param script_tags [Array<Integer>] Candidate script tags (in preference order)
112
+ # @return [Array] [Boolean, Integer, Integer] exact_match, script_index, chosen_script_tag
113
+ def select_script(face, table_tag, script_tags)
114
+ tags_ptr = FFI::MemoryPointer.new(:uint32, script_tags.size)
115
+ tags_ptr.put_array_of_uint32(0, script_tags)
116
+ idx_ptr = FFI::MemoryPointer.new(:uint)
117
+ chosen_tag_ptr = FFI::MemoryPointer.new(:uint32)
118
+ exact = C.from_hb_bool(
119
+ C.hb_ot_layout_table_select_script(
120
+ face.ptr, table_tag, script_tags.size, tags_ptr, idx_ptr, chosen_tag_ptr
121
+ )
122
+ )
123
+ [exact, idx_ptr.read_uint, chosen_tag_ptr.read_uint32]
124
+ end
125
+
126
+ # Returns language tags for a script
127
+ # @param face [Face] Font face
128
+ # @param table_tag [Integer] GSUB or GPOS tag
129
+ # @param script_index [Integer] Script index
130
+ # @return [Array<Integer>] Language tags
131
+ def language_tags(face, table_tag, script_index)
132
+ count_ptr = FFI::MemoryPointer.new(:uint)
133
+ count_ptr.write_uint(0)
134
+ total = C.hb_ot_layout_script_get_language_tags(
135
+ face.ptr, table_tag, script_index, 0, count_ptr, nil
136
+ )
137
+ return [] if total.zero?
138
+
139
+ tags_ptr = FFI::MemoryPointer.new(:uint32, total)
140
+ count_ptr.write_uint(total)
141
+ C.hb_ot_layout_script_get_language_tags(
142
+ face.ptr, table_tag, script_index, 0, count_ptr, tags_ptr
143
+ )
144
+ tags_ptr.read_array_of_uint32(count_ptr.read_uint)
145
+ end
146
+
147
+ # Selects the best matching language, returns [found, language_index]
148
+ # @param face [Face] Font face
149
+ # @param table_tag [Integer] GSUB or GPOS tag
150
+ # @param script_index [Integer] Script index
151
+ # @param language_tags [Array<Integer>] Candidate language tags
152
+ # @return [Array] [Boolean, Integer] found and language index
153
+ def select_language(face, table_tag, script_index, language_tags)
154
+ tags_ptr = FFI::MemoryPointer.new(:uint32, language_tags.size)
155
+ tags_ptr.put_array_of_uint32(0, language_tags)
156
+ lang_idx_ptr = FFI::MemoryPointer.new(:uint)
157
+ found = C.from_hb_bool(
158
+ C.hb_ot_layout_script_select_language(
159
+ face.ptr, table_tag, script_index,
160
+ language_tags.size, tags_ptr, lang_idx_ptr
161
+ )
162
+ )
163
+ [found, lang_idx_ptr.read_uint]
164
+ end
165
+
166
+ # Returns the required feature index for a language, or nil if none
167
+ # @param face [Face] Font face
168
+ # @param table_tag [Integer] GSUB or GPOS tag
169
+ # @param script_index [Integer] Script index
170
+ # @param language_index [Integer] Language index
171
+ # @return [Integer, nil] Feature index or nil
172
+ def required_feature_index(face, table_tag, script_index, language_index)
173
+ idx_ptr = FFI::MemoryPointer.new(:uint)
174
+ found = C.from_hb_bool(
175
+ C.hb_ot_layout_language_get_required_feature_index(
176
+ face.ptr, table_tag, script_index, language_index, idx_ptr
177
+ )
178
+ )
179
+ found ? idx_ptr.read_uint : nil
180
+ end
181
+
182
+ # Returns [feature_index, feature_tag] for the required feature, or nil
183
+ # @param face [Face] Font face
184
+ # @param table_tag [Integer] GSUB or GPOS tag
185
+ # @param script_index [Integer] Script index
186
+ # @param language_index [Integer] Language index
187
+ # @return [Array, nil] [feature_index, feature_tag] or nil
188
+ def required_feature(face, table_tag, script_index, language_index)
189
+ idx_ptr = FFI::MemoryPointer.new(:uint)
190
+ tag_ptr = FFI::MemoryPointer.new(:uint32)
191
+ found = C.from_hb_bool(
192
+ C.hb_ot_layout_language_get_required_feature(
193
+ face.ptr, table_tag, script_index, language_index, idx_ptr, tag_ptr
194
+ )
195
+ )
196
+ found ? [idx_ptr.read_uint, tag_ptr.read_uint32] : nil
197
+ end
198
+
199
+ # Returns feature indices for a language
200
+ # @param face [Face] Font face
201
+ # @param table_tag [Integer] GSUB or GPOS tag
202
+ # @param script_index [Integer] Script index
203
+ # @param language_index [Integer] Language index
204
+ # @return [Array<Integer>] Feature indices
205
+ def feature_indexes(face, table_tag, script_index, language_index)
206
+ count_ptr = FFI::MemoryPointer.new(:uint)
207
+ count_ptr.write_uint(0)
208
+ total = C.hb_ot_layout_language_get_feature_indexes(
209
+ face.ptr, table_tag, script_index, language_index, 0, count_ptr, nil
210
+ )
211
+ return [] if total.zero?
212
+
213
+ idx_ptr = FFI::MemoryPointer.new(:uint, total)
214
+ count_ptr.write_uint(total)
215
+ C.hb_ot_layout_language_get_feature_indexes(
216
+ face.ptr, table_tag, script_index, language_index, 0, count_ptr, idx_ptr
217
+ )
218
+ idx_ptr.read_array_of_uint(count_ptr.read_uint)
219
+ end
220
+
221
+ # Returns feature tags for a language
222
+ # @param face [Face] Font face
223
+ # @param table_tag [Integer] GSUB or GPOS tag
224
+ # @param script_index [Integer] Script index
225
+ # @param language_index [Integer] Language index
226
+ # @return [Array<Integer>] Feature tags
227
+ def feature_tags_for_lang(face, table_tag, script_index, language_index)
228
+ count_ptr = FFI::MemoryPointer.new(:uint)
229
+ count_ptr.write_uint(0)
230
+ total = C.hb_ot_layout_language_get_feature_tags(
231
+ face.ptr, table_tag, script_index, language_index, 0, count_ptr, nil
232
+ )
233
+ return [] if total.zero?
234
+
235
+ tags_ptr = FFI::MemoryPointer.new(:uint32, total)
236
+ count_ptr.write_uint(total)
237
+ C.hb_ot_layout_language_get_feature_tags(
238
+ face.ptr, table_tag, script_index, language_index, 0, count_ptr, tags_ptr
239
+ )
240
+ tags_ptr.read_array_of_uint32(count_ptr.read_uint)
241
+ end
242
+
243
+ # Finds a specific feature in a language, returns [found, feature_index]
244
+ # @param face [Face] Font face
245
+ # @param table_tag [Integer] GSUB or GPOS tag
246
+ # @param script_index [Integer] Script index
247
+ # @param language_index [Integer] Language index
248
+ # @param feature_tag [Integer] Feature tag to find
249
+ # @return [Array] [Boolean, Integer] found and feature index
250
+ def find_feature(face, table_tag, script_index, language_index, feature_tag)
251
+ idx_ptr = FFI::MemoryPointer.new(:uint)
252
+ found = C.from_hb_bool(
253
+ C.hb_ot_layout_language_find_feature(
254
+ face.ptr, table_tag, script_index, language_index, feature_tag, idx_ptr
255
+ )
256
+ )
257
+ [found, idx_ptr.read_uint]
258
+ end
259
+
260
+ # Returns lookup indices for a feature
261
+ # @param face [Face] Font face
262
+ # @param table_tag [Integer] GSUB or GPOS tag
263
+ # @param feature_index [Integer] Feature index
264
+ # @return [Array<Integer>] Lookup indices
265
+ def feature_lookups(face, table_tag, feature_index)
266
+ count_ptr = FFI::MemoryPointer.new(:uint)
267
+ count_ptr.write_uint(0)
268
+ total = C.hb_ot_layout_feature_get_lookups(
269
+ face.ptr, table_tag, feature_index, 0, count_ptr, nil
270
+ )
271
+ return [] if total.zero?
272
+
273
+ idx_ptr = FFI::MemoryPointer.new(:uint, total)
274
+ count_ptr.write_uint(total)
275
+ C.hb_ot_layout_feature_get_lookups(
276
+ face.ptr, table_tag, feature_index, 0, count_ptr, idx_ptr
277
+ )
278
+ idx_ptr.read_array_of_uint(count_ptr.read_uint)
279
+ end
280
+
281
+ # Returns size design parameters for the face
282
+ # @param face [Face] Font face
283
+ # @return [Hash, nil] Size params hash or nil if not available
284
+ def size_params(face)
285
+ design_size_ptr = FFI::MemoryPointer.new(:uint)
286
+ subfamily_id_ptr = FFI::MemoryPointer.new(:uint)
287
+ name_id_ptr = FFI::MemoryPointer.new(:uint)
288
+ range_start_ptr = FFI::MemoryPointer.new(:uint)
289
+ range_end_ptr = FFI::MemoryPointer.new(:uint)
290
+ found = C.from_hb_bool(
291
+ C.hb_ot_layout_get_size_params(
292
+ face.ptr,
293
+ design_size_ptr, subfamily_id_ptr, name_id_ptr,
294
+ range_start_ptr, range_end_ptr
295
+ )
296
+ )
297
+ return nil unless found
298
+
299
+ {
300
+ design_size: design_size_ptr.read_uint,
301
+ subfamily_id: subfamily_id_ptr.read_uint,
302
+ subfamily_name_id: name_id_ptr.read_uint,
303
+ range_start: range_start_ptr.read_uint,
304
+ range_end: range_end_ptr.read_uint
305
+ }
306
+ end
307
+
308
+ # Returns OpenType feature UI name IDs
309
+ # @param face [Face] Font face
310
+ # @param table_tag [Integer] GSUB or GPOS tag
311
+ # @param feature_index [Integer] Feature index
312
+ # @return [Hash, nil] Name ID hash or nil
313
+ def feature_name_ids(face, table_tag, feature_index)
314
+ label_id_ptr = FFI::MemoryPointer.new(:uint)
315
+ tooltip_id_ptr = FFI::MemoryPointer.new(:uint)
316
+ sample_id_ptr = FFI::MemoryPointer.new(:uint)
317
+ num_params_ptr = FFI::MemoryPointer.new(:uint)
318
+ first_param_id_ptr = FFI::MemoryPointer.new(:uint)
319
+ found = C.from_hb_bool(
320
+ C.hb_ot_layout_feature_get_name_ids(
321
+ face.ptr, table_tag, feature_index,
322
+ label_id_ptr, tooltip_id_ptr, sample_id_ptr,
323
+ num_params_ptr, first_param_id_ptr
324
+ )
325
+ )
326
+ return nil unless found
327
+
328
+ {
329
+ label_id: label_id_ptr.read_uint,
330
+ tooltip_id: tooltip_id_ptr.read_uint,
331
+ sample_id: sample_id_ptr.read_uint,
332
+ num_named_parameters: num_params_ptr.read_uint,
333
+ first_param_id: first_param_id_ptr.read_uint
334
+ }
335
+ end
336
+
337
+ # Returns codepoints for a named feature's characters
338
+ # @param face [Face] Font face
339
+ # @param table_tag [Integer] GSUB or GPOS tag
340
+ # @param feature_index [Integer] Feature index
341
+ # @return [Array<Integer>] Codepoints
342
+ def feature_characters(face, table_tag, feature_index)
343
+ count_ptr = FFI::MemoryPointer.new(:uint)
344
+ count_ptr.write_uint(0)
345
+ total = C.hb_ot_layout_feature_get_characters(
346
+ face.ptr, table_tag, feature_index, 0, count_ptr, nil
347
+ )
348
+ return [] if total.zero?
349
+
350
+ cps_ptr = FFI::MemoryPointer.new(:uint32, total)
351
+ count_ptr.write_uint(total)
352
+ C.hb_ot_layout_feature_get_characters(
353
+ face.ptr, table_tag, feature_index, 0, count_ptr, cps_ptr
354
+ )
355
+ cps_ptr.read_array_of_uint32(count_ptr.read_uint)
356
+ end
357
+
358
+ # Returns a baseline coordinate value
359
+ # @param font [Font] Font
360
+ # @param baseline_tag [Integer] Baseline tag (HB_OT_LAYOUT_BASELINE_TAG_*)
361
+ # @param dir [Symbol] Direction (:ltr, :rtl, :ttb, :btt)
362
+ # @param script_tag [Integer] Script tag
363
+ # @param language [FFI::Pointer, nil] Language pointer (nil = default)
364
+ # @return [Integer, nil] Baseline coordinate or nil if not available
365
+ def baseline(font, baseline_tag, dir, script_tag, language = nil)
366
+ lang_ptr = language || FFI::Pointer::NULL
367
+ coord_ptr = FFI::MemoryPointer.new(:int32)
368
+ found = C.from_hb_bool(
369
+ C.hb_ot_layout_get_baseline(
370
+ font.ptr, baseline_tag, dir, script_tag, lang_ptr, coord_ptr
371
+ )
372
+ )
373
+ found ? coord_ptr.read_int32 : nil
374
+ end
375
+
376
+ # Returns a baseline coordinate with fallback
377
+ # @param font [Font] Font
378
+ # @param baseline_tag [Integer] Baseline tag
379
+ # @param dir [Symbol] Direction
380
+ # @param script_tag [Integer] Script tag
381
+ # @param language [FFI::Pointer, nil] Language pointer
382
+ # @return [Integer] Baseline coordinate (with fallback)
383
+ def baseline_with_fallback(font, baseline_tag, dir, script_tag, language = nil)
384
+ lang_ptr = language || FFI::Pointer::NULL
385
+ coord_ptr = FFI::MemoryPointer.new(:int32)
386
+ C.hb_ot_layout_get_baseline_with_fallback(
387
+ font.ptr, baseline_tag, dir, script_tag, lang_ptr, coord_ptr
388
+ )
389
+ coord_ptr.read_int32
390
+ end
391
+
392
+ # Returns font extents from OT tables
393
+ # @param font [Font] Font
394
+ # @param dir [Symbol] Direction
395
+ # @param script_tag [Integer] Script tag
396
+ # @param language [FFI::Pointer, nil] Language pointer
397
+ # @return [Hash, nil] Font extents hash or nil
398
+ def font_extents(font, dir, script_tag, language = nil)
399
+ lang_ptr = language || FFI::Pointer::NULL
400
+ extents = C::HbFontExtentsT.new
401
+ found = C.from_hb_bool(
402
+ C.hb_ot_layout_get_font_extents(
403
+ font.ptr, dir, script_tag, lang_ptr, extents.to_ptr
404
+ )
405
+ )
406
+ return nil unless found
407
+
408
+ {
409
+ ascender: extents[:ascender],
410
+ descender: extents[:descender],
411
+ line_gap: extents[:line_gap]
412
+ }
413
+ end
414
+
415
+ # Returns a baseline coordinate value (v2: takes hb_script_t + hb_language_t)
416
+ # @param font [Font] Font
417
+ # @param baseline_tag [Integer] Baseline tag
418
+ # @param dir [Symbol] Direction
419
+ # @param script [Integer] Script value (hb_script_t, e.g. from HarfBuzz.script("Latn"))
420
+ # @param language [FFI::Pointer, nil] Language pointer (nil = default)
421
+ # @return [Integer, nil] Baseline coordinate or nil if not available
422
+ def baseline2(font, baseline_tag, dir, script, language = nil)
423
+ lang_ptr = language || FFI::Pointer::NULL
424
+ coord_ptr = FFI::MemoryPointer.new(:int32)
425
+ found = C.from_hb_bool(
426
+ C.hb_ot_layout_get_baseline2(
427
+ font.ptr, baseline_tag, dir, script, lang_ptr, coord_ptr
428
+ )
429
+ )
430
+ found ? coord_ptr.read_int32 : nil
431
+ end
432
+
433
+ # Returns a baseline coordinate with fallback (v2: takes hb_script_t + hb_language_t)
434
+ # @param font [Font] Font
435
+ # @param baseline_tag [Integer] Baseline tag
436
+ # @param dir [Symbol] Direction
437
+ # @param script [Integer] Script value (hb_script_t)
438
+ # @param language [FFI::Pointer, nil] Language pointer
439
+ # @return [Integer] Baseline coordinate (with fallback)
440
+ def baseline_with_fallback2(font, baseline_tag, dir, script, language = nil)
441
+ lang_ptr = language || FFI::Pointer::NULL
442
+ coord_ptr = FFI::MemoryPointer.new(:int32)
443
+ C.hb_ot_layout_get_baseline_with_fallback2(
444
+ font.ptr, baseline_tag, dir, script, lang_ptr, coord_ptr
445
+ )
446
+ coord_ptr.read_int32
447
+ end
448
+
449
+ # Returns font extents from OT tables (v2: takes hb_script_t + hb_language_t)
450
+ # @param font [Font] Font
451
+ # @param dir [Symbol] Direction
452
+ # @param script [Integer] Script value (hb_script_t)
453
+ # @param language [FFI::Pointer, nil] Language pointer
454
+ # @return [Hash, nil] Font extents hash or nil
455
+ def font_extents2(font, dir, script, language = nil)
456
+ lang_ptr = language || FFI::Pointer::NULL
457
+ extents = C::HbFontExtentsT.new
458
+ found = C.from_hb_bool(
459
+ C.hb_ot_layout_get_font_extents2(
460
+ font.ptr, dir, script, lang_ptr, extents.to_ptr
461
+ )
462
+ )
463
+ return nil unless found
464
+
465
+ {
466
+ ascender: extents[:ascender],
467
+ descender: extents[:descender],
468
+ line_gap: extents[:line_gap]
469
+ }
470
+ end
471
+
472
+ # Returns the horizontal baseline tag for a script
473
+ # @param script [Integer] Script value
474
+ # @return [Integer] Baseline tag
475
+ def horizontal_baseline_tag_for_script(script)
476
+ C.hb_ot_layout_get_horizontal_baseline_tag_for_script(script)
477
+ end
478
+
479
+ # Returns the feature tags for a table
480
+ # @param face [Face] Font face
481
+ # @param table_tag [Integer] GSUB or GPOS tag
482
+ # @return [Array<Integer>] Feature tags
483
+ def feature_tags(face, table_tag)
484
+ count_ptr = FFI::MemoryPointer.new(:uint)
485
+ count_ptr.write_uint(0)
486
+ C.hb_ot_layout_table_get_feature_tags(face.ptr, table_tag, 0, count_ptr, nil)
487
+ count = count_ptr.read_uint
488
+ return [] if count.zero?
489
+
490
+ tags_ptr = FFI::MemoryPointer.new(:uint32, count)
491
+ count_ptr.write_uint(count)
492
+ C.hb_ot_layout_table_get_feature_tags(face.ptr, table_tag, 0, count_ptr, tags_ptr)
493
+ tags_ptr.read_array_of_uint32(count_ptr.read_uint)
494
+ end
495
+
496
+ # Collects all lookups referenced by features
497
+ # @param face [Face] Font face
498
+ # @param table_tag [Integer] GSUB or GPOS tag
499
+ # @param scripts [Array<Integer>, nil] Script tags filter (nil = all)
500
+ # @param languages [Array<Integer>, nil] Language tags filter (nil = all)
501
+ # @param features [Array<Integer>, nil] Feature tags filter (nil = all)
502
+ # @return [HarfBuzz::Set] Set of lookup indices
503
+ def collect_lookups(face, table_tag, scripts: nil, languages: nil, features: nil)
504
+ set = HarfBuzz::Set.new
505
+ scripts_ptr = build_tag_array_ptr(scripts)
506
+ languages_ptr = build_tag_array_ptr(languages)
507
+ features_ptr = build_tag_array_ptr(features)
508
+ C.hb_ot_layout_collect_lookups(
509
+ face.ptr, table_tag, scripts_ptr, languages_ptr, features_ptr, set.ptr
510
+ )
511
+ set
512
+ end
513
+
514
+ # Collects all feature indices
515
+ # @param face [Face] Font face
516
+ # @param table_tag [Integer] GSUB or GPOS tag
517
+ # @return [HarfBuzz::Set] Set of feature indices
518
+ def collect_features(face, table_tag, scripts: nil, languages: nil, features: nil)
519
+ set = HarfBuzz::Set.new
520
+ scripts_ptr = build_tag_array_ptr(scripts)
521
+ languages_ptr = build_tag_array_ptr(languages)
522
+ features_ptr = build_tag_array_ptr(features)
523
+ C.hb_ot_layout_collect_features(
524
+ face.ptr, table_tag, scripts_ptr, languages_ptr, features_ptr, set.ptr
525
+ )
526
+ set
527
+ end
528
+
529
+ # Converts script+language to OT tags
530
+ # @param script [Integer] Script value
531
+ # @param language [FFI::Pointer] Language pointer
532
+ # @return [Array<Array<Integer>>] [script_tags, language_tags]
533
+ def tags_from_script_and_language(script, language)
534
+ s_count = FFI::MemoryPointer.new(:uint)
535
+ l_count = FFI::MemoryPointer.new(:uint)
536
+ s_count.write_uint(8)
537
+ l_count.write_uint(8)
538
+ s_tags = FFI::MemoryPointer.new(:uint32, 8)
539
+ l_tags = FFI::MemoryPointer.new(:uint32, 8)
540
+ C.hb_ot_tags_from_script_and_language(script, language, s_count, s_tags, l_count, l_tags)
541
+ [
542
+ s_tags.read_array_of_uint32(s_count.read_uint),
543
+ l_tags.read_array_of_uint32(l_count.read_uint)
544
+ ]
545
+ end
546
+
547
+ # Converts OT tags to script + language
548
+ # @param script_tag [Integer] OT script tag
549
+ # @param language_tag [Integer] OT language tag
550
+ # @return [Array] [script, language_ptr]
551
+ def tags_to_script_and_language(script_tag, language_tag)
552
+ script_ptr = FFI::MemoryPointer.new(:uint32)
553
+ lang_ptr = FFI::MemoryPointer.new(:pointer)
554
+ C.hb_ot_tags_to_script_and_language(script_tag, language_tag, script_ptr, lang_ptr)
555
+ [script_ptr.read_uint32, lang_ptr.read_pointer]
556
+ end
557
+
558
+ # Converts an OT tag to a language pointer
559
+ # @param tag [Integer] Language tag
560
+ # @return [FFI::Pointer] Language pointer
561
+ def tag_to_language(tag)
562
+ C.hb_ot_tag_to_language(tag)
563
+ end
564
+
565
+ # Converts an OT tag to a script value
566
+ # @param tag [Integer] Script tag
567
+ # @return [Integer] Script value
568
+ def tag_to_script(tag)
569
+ C.hb_ot_tag_to_script(tag)
570
+ end
571
+
572
+ def build_tag_array_ptr(tags)
573
+ return FFI::Pointer::NULL unless tags
574
+
575
+ ptr = FFI::MemoryPointer.new(:uint32, tags.size + 1)
576
+ ptr.put_array_of_uint32(0, tags)
577
+ ptr.put_uint32(tags.size * 4, HarfBuzz::C::HB_SET_VALUE_INVALID)
578
+ ptr
579
+ end
580
+ private_class_method :build_tag_array_ptr
581
+ end
582
+ end
583
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HarfBuzz
4
+ module OT
5
+ # OpenType Math Typesetting API
6
+ module Math
7
+ module_function
8
+
9
+ # @param face [Face] Font face
10
+ # @return [Boolean] true if the face has a MATH table
11
+ def has_data?(face)
12
+ C.from_hb_bool(C.hb_ot_math_has_data(face.ptr))
13
+ end
14
+
15
+ # Returns a MATH constant value
16
+ # @param font [Font] Sized font
17
+ # @param constant [Integer] Math constant index
18
+ # @return [Integer] Constant value in font units
19
+ def constant(font, constant)
20
+ C.hb_ot_math_get_constant(font.ptr, constant)
21
+ end
22
+
23
+ # @param font [Font] Sized font
24
+ # @param glyph [Integer] Glyph ID
25
+ # @return [Integer] Italics correction in font units
26
+ def glyph_italics_correction(font, glyph)
27
+ C.hb_ot_math_get_glyph_italics_correction(font.ptr, glyph)
28
+ end
29
+
30
+ # @param font [Font] Sized font
31
+ # @param glyph [Integer] Glyph ID
32
+ # @return [Integer] Top accent attachment in font units
33
+ def glyph_top_accent_attachment(font, glyph)
34
+ C.hb_ot_math_get_glyph_top_accent_attachment(font.ptr, glyph)
35
+ end
36
+
37
+ # @param face [Face] Font face
38
+ # @param glyph [Integer] Glyph ID
39
+ # @return [Boolean] true if glyph is an extended shape
40
+ def glyph_extended_shape?(face, glyph)
41
+ C.from_hb_bool(C.hb_ot_math_is_glyph_extended_shape(face.ptr, glyph))
42
+ end
43
+
44
+ # Returns the math kerning value
45
+ # @param font [Font] Sized font
46
+ # @param glyph [Integer] Glyph ID
47
+ # @param kern [Integer] Kern type
48
+ # @param correction_height [Integer] Correction height
49
+ # @return [Integer] Kerning value
50
+ def glyph_kerning(font, glyph, kern, correction_height)
51
+ C.hb_ot_math_get_glyph_kerning(font.ptr, glyph, kern, correction_height)
52
+ end
53
+
54
+ # Returns math glyph variants
55
+ # @param font [Font] Sized font
56
+ # @param glyph [Integer] Glyph ID
57
+ # @param dir [Symbol] Direction
58
+ # @return [Array<C::HbOtMathGlyphVariantT>] Variants
59
+ def glyph_variants(font, glyph, dir)
60
+ count_ptr = FFI::MemoryPointer.new(:uint)
61
+ count_ptr.write_uint(0)
62
+ C.hb_ot_math_get_glyph_variants(font.ptr, glyph, dir, 0, count_ptr, nil)
63
+ count = count_ptr.read_uint
64
+ return [] if count.zero?
65
+
66
+ variants_ptr = FFI::MemoryPointer.new(C::HbOtMathGlyphVariantT, count)
67
+ count_ptr.write_uint(count)
68
+ C.hb_ot_math_get_glyph_variants(font.ptr, glyph, dir, 0, count_ptr, variants_ptr)
69
+ actual = count_ptr.read_uint
70
+ actual.times.map do |i|
71
+ C::HbOtMathGlyphVariantT.new(variants_ptr + i * C::HbOtMathGlyphVariantT.size)
72
+ end
73
+ end
74
+
75
+ # Returns math glyph assembly (extensible glyph parts)
76
+ # @param font [Font] Sized font
77
+ # @param glyph [Integer] Glyph ID
78
+ # @param dir [Symbol] Direction
79
+ # @return [Hash] { parts: Array<C::HbOtMathGlyphPartT>, italics_correction: Integer }
80
+ def glyph_assembly(font, glyph, dir)
81
+ count_ptr = FFI::MemoryPointer.new(:uint)
82
+ count_ptr.write_uint(0)
83
+ italics_ptr = FFI::MemoryPointer.new(:int32)
84
+ total = C.hb_ot_math_get_glyph_assembly(
85
+ font.ptr, glyph, dir, 0, count_ptr, nil, italics_ptr
86
+ )
87
+ parts = if total.zero?
88
+ []
89
+ else
90
+ parts_ptr = FFI::MemoryPointer.new(C::HbOtMathGlyphPartT, total)
91
+ count_ptr.write_uint(total)
92
+ C.hb_ot_math_get_glyph_assembly(
93
+ font.ptr, glyph, dir, 0, count_ptr, parts_ptr, italics_ptr
94
+ )
95
+ actual = count_ptr.read_uint
96
+ actual.times.map do |i|
97
+ C::HbOtMathGlyphPartT.new(parts_ptr + i * C::HbOtMathGlyphPartT.size)
98
+ end
99
+ end
100
+ { parts: parts, italics_correction: italics_ptr.read_int32 }
101
+ end
102
+
103
+ # @param font [Font] Sized font
104
+ # @param dir [Symbol] Direction
105
+ # @return [Integer] Minimum connector overlap in font units
106
+ def min_connector_overlap(font, dir)
107
+ C.hb_ot_math_get_min_connector_overlap(font.ptr, dir)
108
+ end
109
+ end
110
+ end
111
+ end