harfbuzz-ruby 1.0.0 → 1.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -1
- data/README.md +12 -6
- data/benchmark/shaping_bench.rb +57 -0
- data/lib/harfbuzz/buffer.rb +103 -0
- data/lib/harfbuzz/version.rb +1 -1
- data/sig/harfbuzz.rbs +4 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ba8458f950bfa90bc1c6be8d9d23c8096424cf3eb01383aeedae3678cd49c0c9
|
|
4
|
+
data.tar.gz: 94631d375acb53a4ab4ae47a63fa6d0e1cae7fe38577fe0a27663086417cf3de
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f623f5a924225ae41e3c5d71f0a02cdb0d4270c9cf57067cb64fd670b6e87d3a7deacd51e382abc7c836b8ec30a5413da3726e969eb9b70b66cb800e3d6d3c14
|
|
7
|
+
data.tar.gz: b11384daed8e7d3f76deb8f6f21f5623af3e039543843ea0b649bbaaa39b028000aabc2bae6d19ecd73ed74ecb558bf20f3df1a582e522bb0ce9285cab74c92b
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
- Add `Buffer#each_glyph_with_clusters` for yielding the next distinct cluster without changing `Buffer#each_glyph`'s existing six-value interface.
|
|
11
|
+
|
|
10
12
|
## v1.0.0 (2026-02-27)
|
|
11
13
|
|
|
12
|
-
- Initial release.
|
|
14
|
+
- Initial release.
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# HarfBuzz Ruby
|
|
1
|
+
# HarfBuzz Ruby [](https://badge.fury.io/rb/harfbuzz-ruby) [](https://github.com/ydah/harfbuzz-ruby/actions/workflows/main.yml)
|
|
2
2
|
|
|
3
3
|
Ruby bindings for [HarfBuzz](https://harfbuzz.github.io/) text shaping engine.
|
|
4
4
|
|
|
@@ -90,20 +90,26 @@ face = HarfBuzz::Face.new(blob, 0)
|
|
|
90
90
|
font = HarfBuzz::Font.new(face)
|
|
91
91
|
|
|
92
92
|
# Create buffer and add text
|
|
93
|
+
text = "Hello, World!"
|
|
93
94
|
buffer = HarfBuzz::Buffer.new
|
|
94
|
-
buffer.add_utf8(
|
|
95
|
+
buffer.add_utf8(text)
|
|
95
96
|
buffer.guess_segment_properties
|
|
96
97
|
|
|
97
98
|
# Shape
|
|
98
99
|
HarfBuzz.shape(font, buffer)
|
|
99
100
|
|
|
100
|
-
# Read results
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
# Read results without allocating GlyphInfo/GlyphPosition wrappers
|
|
102
|
+
last_cluster = nil
|
|
103
|
+
buffer.each_glyph_with_clusters do |glyph_id, cluster, next_cluster, x_advance, _y_advance, x_offset, _y_offset|
|
|
104
|
+
source = cluster == last_cluster ? "" : text.byteslice(cluster...(next_cluster || text.bytesize))
|
|
105
|
+
puts "glyph_id=#{glyph_id} source=#{source.inspect} cluster=#{cluster} next_cluster=#{next_cluster} " \
|
|
106
|
+
"x_advance=#{x_advance} x_offset=#{x_offset}"
|
|
107
|
+
last_cluster = cluster
|
|
104
108
|
end
|
|
105
109
|
```
|
|
106
110
|
|
|
111
|
+
`Buffer#each_glyph` yields the original six values. `Buffer#each_glyph_with_clusters` adds `next_cluster` as the third value; it is the next distinct cluster in glyph order, or `nil` for the last cluster.
|
|
112
|
+
|
|
107
113
|
### Shaping with Features
|
|
108
114
|
|
|
109
115
|
```ruby
|
data/benchmark/shaping_bench.rb
CHANGED
|
@@ -28,6 +28,11 @@ face = HarfBuzz::Face.new(blob, 0)
|
|
|
28
28
|
font = HarfBuzz::Font.new(face)
|
|
29
29
|
font.make_immutable!
|
|
30
30
|
|
|
31
|
+
iteration_buffer = HarfBuzz::Buffer.new
|
|
32
|
+
iteration_buffer.add_utf8("Hello World " * 20)
|
|
33
|
+
iteration_buffer.guess_segment_properties
|
|
34
|
+
HarfBuzz.shape(font, iteration_buffer)
|
|
35
|
+
|
|
31
36
|
Benchmark.ips do |x|
|
|
32
37
|
x.config(time: 5, warmup: 2)
|
|
33
38
|
|
|
@@ -55,6 +60,58 @@ Benchmark.ips do |x|
|
|
|
55
60
|
end
|
|
56
61
|
end
|
|
57
62
|
|
|
63
|
+
x.report("iterate glyph wrappers") do
|
|
64
|
+
total = 0
|
|
65
|
+
infos = iteration_buffer.glyph_infos
|
|
66
|
+
positions = iteration_buffer.glyph_positions
|
|
67
|
+
count = [infos.length, positions.length].min
|
|
68
|
+
|
|
69
|
+
i = 0
|
|
70
|
+
while i < count
|
|
71
|
+
cluster = infos[i].cluster
|
|
72
|
+
next_cluster = nil
|
|
73
|
+
next_run_index = i + 1
|
|
74
|
+
|
|
75
|
+
while next_run_index < count
|
|
76
|
+
candidate_cluster = infos[next_run_index].cluster
|
|
77
|
+
if candidate_cluster != cluster
|
|
78
|
+
next_cluster = candidate_cluster
|
|
79
|
+
break
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
next_run_index += 1
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
while i < next_run_index
|
|
86
|
+
info = infos[i]
|
|
87
|
+
position = positions[i]
|
|
88
|
+
total += info.glyph_id
|
|
89
|
+
total += cluster
|
|
90
|
+
total += next_cluster || 0
|
|
91
|
+
total += position.x_advance
|
|
92
|
+
total += position.y_advance
|
|
93
|
+
total += position.x_offset
|
|
94
|
+
total += position.y_offset
|
|
95
|
+
i += 1
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
total
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
x.report("iterate each_glyph_with_clusters") do
|
|
102
|
+
total = 0
|
|
103
|
+
iteration_buffer.each_glyph_with_clusters do |glyph_id, cluster, next_cluster, x_advance, y_advance, x_offset, y_offset|
|
|
104
|
+
total += glyph_id
|
|
105
|
+
total += cluster
|
|
106
|
+
total += next_cluster || 0
|
|
107
|
+
total += x_advance
|
|
108
|
+
total += y_advance
|
|
109
|
+
total += x_offset
|
|
110
|
+
total += y_offset
|
|
111
|
+
end
|
|
112
|
+
total
|
|
113
|
+
end
|
|
114
|
+
|
|
58
115
|
x.report("create + destroy buffer") do
|
|
59
116
|
HarfBuzz::Buffer.new
|
|
60
117
|
end
|
data/lib/harfbuzz/buffer.rb
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
module HarfBuzz
|
|
4
4
|
# Wraps hb_buffer_t — text buffer for shaping
|
|
5
5
|
class Buffer
|
|
6
|
+
GLYPH_INFO_SIZE = C::HbGlyphInfoT.size
|
|
7
|
+
GLYPH_INFO_CODEPOINT_OFFSET = C::HbGlyphInfoT.offset_of(:codepoint)
|
|
8
|
+
GLYPH_INFO_CLUSTER_OFFSET = C::HbGlyphInfoT.offset_of(:cluster)
|
|
9
|
+
GLYPH_POSITION_SIZE = C::HbGlyphPositionT.size
|
|
10
|
+
GLYPH_POSITION_X_ADVANCE_OFFSET = C::HbGlyphPositionT.offset_of(:x_advance)
|
|
11
|
+
GLYPH_POSITION_Y_ADVANCE_OFFSET = C::HbGlyphPositionT.offset_of(:y_advance)
|
|
12
|
+
GLYPH_POSITION_X_OFFSET_OFFSET = C::HbGlyphPositionT.offset_of(:x_offset)
|
|
13
|
+
GLYPH_POSITION_Y_OFFSET_OFFSET = C::HbGlyphPositionT.offset_of(:y_offset)
|
|
14
|
+
private_constant :GLYPH_INFO_SIZE,
|
|
15
|
+
:GLYPH_INFO_CODEPOINT_OFFSET,
|
|
16
|
+
:GLYPH_INFO_CLUSTER_OFFSET,
|
|
17
|
+
:GLYPH_POSITION_SIZE,
|
|
18
|
+
:GLYPH_POSITION_X_ADVANCE_OFFSET,
|
|
19
|
+
:GLYPH_POSITION_Y_ADVANCE_OFFSET,
|
|
20
|
+
:GLYPH_POSITION_X_OFFSET_OFFSET,
|
|
21
|
+
:GLYPH_POSITION_Y_OFFSET_OFFSET
|
|
22
|
+
|
|
6
23
|
attr_reader :ptr
|
|
7
24
|
|
|
8
25
|
# Creates a new empty buffer
|
|
@@ -310,6 +327,78 @@ module HarfBuzz
|
|
|
310
327
|
end
|
|
311
328
|
end
|
|
312
329
|
|
|
330
|
+
# Iterates over shaped glyph data without allocating GlyphInfo/GlyphPosition objects
|
|
331
|
+
# @yield [glyph_id, cluster, x_advance, y_advance, x_offset, y_offset]
|
|
332
|
+
# @return [Enumerator, self]
|
|
333
|
+
def each_glyph
|
|
334
|
+
return enum_for(__method__) { length } unless block_given?
|
|
335
|
+
|
|
336
|
+
with_glyph_data do |infos_ptr, positions_ptr, count|
|
|
337
|
+
i = 0
|
|
338
|
+
while i < count
|
|
339
|
+
info_offset = i * GLYPH_INFO_SIZE
|
|
340
|
+
position_offset = i * GLYPH_POSITION_SIZE
|
|
341
|
+
|
|
342
|
+
yield infos_ptr.get_uint32(info_offset + GLYPH_INFO_CODEPOINT_OFFSET),
|
|
343
|
+
infos_ptr.get_uint32(info_offset + GLYPH_INFO_CLUSTER_OFFSET),
|
|
344
|
+
positions_ptr.get_int32(position_offset + GLYPH_POSITION_X_ADVANCE_OFFSET),
|
|
345
|
+
positions_ptr.get_int32(position_offset + GLYPH_POSITION_Y_ADVANCE_OFFSET),
|
|
346
|
+
positions_ptr.get_int32(position_offset + GLYPH_POSITION_X_OFFSET_OFFSET),
|
|
347
|
+
positions_ptr.get_int32(position_offset + GLYPH_POSITION_Y_OFFSET_OFFSET)
|
|
348
|
+
|
|
349
|
+
i += 1
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
self
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Iterates over shaped glyph data and yields the next distinct cluster without allocating
|
|
357
|
+
# GlyphInfo/GlyphPosition objects.
|
|
358
|
+
# @yield [glyph_id, cluster, next_cluster, x_advance, y_advance, x_offset, y_offset]
|
|
359
|
+
# @return [Enumerator, self]
|
|
360
|
+
def each_glyph_with_clusters
|
|
361
|
+
return enum_for(__method__) { length } unless block_given?
|
|
362
|
+
|
|
363
|
+
with_glyph_data do |infos_ptr, positions_ptr, count|
|
|
364
|
+
i = 0
|
|
365
|
+
while i < count
|
|
366
|
+
info_offset = i * GLYPH_INFO_SIZE
|
|
367
|
+
cluster = infos_ptr.get_uint32(info_offset + GLYPH_INFO_CLUSTER_OFFSET)
|
|
368
|
+
next_cluster = nil
|
|
369
|
+
|
|
370
|
+
next_run_index = i + 1
|
|
371
|
+
while next_run_index < count
|
|
372
|
+
next_cluster_offset = (next_run_index * GLYPH_INFO_SIZE) + GLYPH_INFO_CLUSTER_OFFSET
|
|
373
|
+
candidate_cluster = infos_ptr.get_uint32(next_cluster_offset)
|
|
374
|
+
if candidate_cluster != cluster
|
|
375
|
+
next_cluster = candidate_cluster
|
|
376
|
+
break
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
next_run_index += 1
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
while i < next_run_index
|
|
383
|
+
info_offset = i * GLYPH_INFO_SIZE
|
|
384
|
+
position_offset = i * GLYPH_POSITION_SIZE
|
|
385
|
+
|
|
386
|
+
yield infos_ptr.get_uint32(info_offset + GLYPH_INFO_CODEPOINT_OFFSET),
|
|
387
|
+
cluster,
|
|
388
|
+
next_cluster,
|
|
389
|
+
positions_ptr.get_int32(position_offset + GLYPH_POSITION_X_ADVANCE_OFFSET),
|
|
390
|
+
positions_ptr.get_int32(position_offset + GLYPH_POSITION_Y_ADVANCE_OFFSET),
|
|
391
|
+
positions_ptr.get_int32(position_offset + GLYPH_POSITION_X_OFFSET_OFFSET),
|
|
392
|
+
positions_ptr.get_int32(position_offset + GLYPH_POSITION_Y_OFFSET_OFFSET)
|
|
393
|
+
|
|
394
|
+
i += 1
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
self
|
|
400
|
+
end
|
|
401
|
+
|
|
313
402
|
# @return [Boolean] true if the buffer has glyph position data
|
|
314
403
|
def has_positions?
|
|
315
404
|
C.from_hb_bool(C.hb_buffer_has_positions(@ptr))
|
|
@@ -483,6 +572,20 @@ module HarfBuzz
|
|
|
483
572
|
|
|
484
573
|
private
|
|
485
574
|
|
|
575
|
+
def with_glyph_data
|
|
576
|
+
info_length_ptr = FFI::MemoryPointer.new(:uint)
|
|
577
|
+
infos_ptr = C.hb_buffer_get_glyph_infos(@ptr, info_length_ptr)
|
|
578
|
+
position_length_ptr = FFI::MemoryPointer.new(:uint)
|
|
579
|
+
positions_ptr = C.hb_buffer_get_glyph_positions(@ptr, position_length_ptr)
|
|
580
|
+
|
|
581
|
+
return if infos_ptr.null? || positions_ptr.null?
|
|
582
|
+
|
|
583
|
+
count = [info_length_ptr.read_uint, position_length_ptr.read_uint].min
|
|
584
|
+
return if count.zero?
|
|
585
|
+
|
|
586
|
+
yield infos_ptr, positions_ptr, count
|
|
587
|
+
end
|
|
588
|
+
|
|
486
589
|
def register_finalizer
|
|
487
590
|
return if instance_variable_defined?(:@borrowed) && @borrowed
|
|
488
591
|
|
data/lib/harfbuzz/version.rb
CHANGED
data/sig/harfbuzz.rbs
CHANGED
|
@@ -140,6 +140,10 @@ module HarfBuzz
|
|
|
140
140
|
|
|
141
141
|
def glyph_infos: () -> Array[GlyphInfo]
|
|
142
142
|
def glyph_positions: () -> Array[GlyphPosition]
|
|
143
|
+
def each_glyph: () { (Integer glyph_id, Integer cluster, Integer x_advance, Integer y_advance, Integer x_offset, Integer y_offset) -> void } -> self
|
|
144
|
+
| () -> Enumerator[[Integer, Integer, Integer, Integer, Integer, Integer], self]
|
|
145
|
+
def each_glyph_with_clusters: () { (Integer glyph_id, Integer cluster, Integer? next_cluster, Integer x_advance, Integer y_advance, Integer x_offset, Integer y_offset) -> void } -> self
|
|
146
|
+
| () -> Enumerator[[Integer, Integer, Integer?, Integer, Integer, Integer, Integer], self]
|
|
143
147
|
def has_positions?: () -> bool
|
|
144
148
|
|
|
145
149
|
def normalize_glyphs: () -> self
|