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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b56e40fbf6e75fcf2cecdcfce3d2dc685281a3283c60b96f6a6bac6c28f16fc1
4
- data.tar.gz: 6c1137acb54689eedf6a05da99e47e44ea83b000cf0b98a129bdfbc4c60074ad
3
+ metadata.gz: ba8458f950bfa90bc1c6be8d9d23c8096424cf3eb01383aeedae3678cd49c0c9
4
+ data.tar.gz: 94631d375acb53a4ab4ae47a63fa6d0e1cae7fe38577fe0a27663086417cf3de
5
5
  SHA512:
6
- metadata.gz: d4f119a1c98ec67296ec7351bd36943e1a067341305cd7c3ee7e9a1efb7d42c7a8515273951017af8dbe212f3a4252827108c40332297e2dfb2d323595c3ea25
7
- data.tar.gz: 2bc37ddb7b27ef540fef758b52a2708d5df8ec41fd96763680e85adcbe43c667ede6f3dd9873b57f381847ff129c6a60398e1773fa499498e5933a146a8b9d5d
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 [![Gem Version](https://badge.fury.io/rb/harfbuzz-ruby.svg)](https://badge.fury.io/rb/harfbuzz-ruby) [![CI](https://github.com/ydah/harfbuzz-ruby/actions/workflows/main.yml/badge.svg)](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("Hello, World!")
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
- buffer.glyph_infos.zip(buffer.glyph_positions).each do |info, pos|
102
- puts "glyph_id=#{info.glyph_id} cluster=#{info.cluster} " \
103
- "x_advance=#{pos.x_advance} x_offset=#{pos.x_offset}"
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
@@ -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
@@ -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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HarfBuzz
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: harfbuzz-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yudai Takada