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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +258 -0
- data/Rakefile +8 -0
- data/benchmark/shaping_bench.rb +77 -0
- data/examples/basic_shaping.rb +67 -0
- data/examples/glyph_outlines.rb +79 -0
- data/examples/opentype_features.rb +91 -0
- data/examples/render_svg.rb +112 -0
- data/examples/render_waterfall.rb +177 -0
- data/examples/variable_fonts.rb +73 -0
- data/lib/harfbuzz/aat/layout.rb +78 -0
- data/lib/harfbuzz/blob.rb +136 -0
- data/lib/harfbuzz/buffer.rb +497 -0
- data/lib/harfbuzz/c/aat/layout.rb +15 -0
- data/lib/harfbuzz/c/base.rb +114 -0
- data/lib/harfbuzz/c/blob.rb +23 -0
- data/lib/harfbuzz/c/buffer.rb +127 -0
- data/lib/harfbuzz/c/common.rb +39 -0
- data/lib/harfbuzz/c/draw.rb +22 -0
- data/lib/harfbuzz/c/enums.rb +146 -0
- data/lib/harfbuzz/c/face.rb +37 -0
- data/lib/harfbuzz/c/font.rb +88 -0
- data/lib/harfbuzz/c/font_funcs.rb +58 -0
- data/lib/harfbuzz/c/map.rb +28 -0
- data/lib/harfbuzz/c/ot/color.rb +32 -0
- data/lib/harfbuzz/c/ot/font.rb +7 -0
- data/lib/harfbuzz/c/ot/layout.rb +83 -0
- data/lib/harfbuzz/c/ot/math.rb +23 -0
- data/lib/harfbuzz/c/ot/meta.rb +10 -0
- data/lib/harfbuzz/c/ot/metrics.rb +16 -0
- data/lib/harfbuzz/c/ot/name.rb +13 -0
- data/lib/harfbuzz/c/ot/shape.rb +10 -0
- data/lib/harfbuzz/c/ot/var.rb +22 -0
- data/lib/harfbuzz/c/paint.rb +38 -0
- data/lib/harfbuzz/c/set.rb +42 -0
- data/lib/harfbuzz/c/shape.rb +11 -0
- data/lib/harfbuzz/c/shape_plan.rb +24 -0
- data/lib/harfbuzz/c/structs.rb +120 -0
- data/lib/harfbuzz/c/subset.rb +49 -0
- data/lib/harfbuzz/c/unicode.rb +40 -0
- data/lib/harfbuzz/c/version.rb +25 -0
- data/lib/harfbuzz/draw_funcs.rb +112 -0
- data/lib/harfbuzz/error.rb +27 -0
- data/lib/harfbuzz/face.rb +186 -0
- data/lib/harfbuzz/feature.rb +76 -0
- data/lib/harfbuzz/flags.rb +85 -0
- data/lib/harfbuzz/font.rb +404 -0
- data/lib/harfbuzz/font_funcs.rb +286 -0
- data/lib/harfbuzz/glyph_info.rb +35 -0
- data/lib/harfbuzz/glyph_position.rb +41 -0
- data/lib/harfbuzz/library.rb +98 -0
- data/lib/harfbuzz/map.rb +157 -0
- data/lib/harfbuzz/ot/color.rb +125 -0
- data/lib/harfbuzz/ot/font.rb +16 -0
- data/lib/harfbuzz/ot/layout.rb +583 -0
- data/lib/harfbuzz/ot/math.rb +111 -0
- data/lib/harfbuzz/ot/meta.rb +34 -0
- data/lib/harfbuzz/ot/metrics.rb +54 -0
- data/lib/harfbuzz/ot/name.rb +81 -0
- data/lib/harfbuzz/ot/shape.rb +34 -0
- data/lib/harfbuzz/ot/var.rb +116 -0
- data/lib/harfbuzz/paint_funcs.rb +134 -0
- data/lib/harfbuzz/set.rb +272 -0
- data/lib/harfbuzz/shape_plan.rb +115 -0
- data/lib/harfbuzz/shaping_result.rb +94 -0
- data/lib/harfbuzz/subset.rb +130 -0
- data/lib/harfbuzz/unicode_funcs.rb +201 -0
- data/lib/harfbuzz/variation.rb +49 -0
- data/lib/harfbuzz/version.rb +5 -0
- data/lib/harfbuzz-ffi.rb +4 -0
- data/lib/harfbuzz.rb +313 -0
- data/sig/harfbuzz.rbs +594 -0
- metadata +132 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b56e40fbf6e75fcf2cecdcfce3d2dc685281a3283c60b96f6a6bac6c28f16fc1
|
|
4
|
+
data.tar.gz: 6c1137acb54689eedf6a05da99e47e44ea83b000cf0b98a129bdfbc4c60074ad
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d4f119a1c98ec67296ec7351bd36943e1a067341305cd7c3ee7e9a1efb7d42c7a8515273951017af8dbe212f3a4252827108c40332297e2dfb2d323595c3ea25
|
|
7
|
+
data.tar.gz: 2bc37ddb7b27ef540fef758b52a2708d5df8ec41fd96763680e85adcbe43c667ede6f3dd9873b57f381847ff129c6a60398e1773fa499498e5933a146a8b9d5d
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## Unreleased
|
|
9
|
+
|
|
10
|
+
## v1.0.0 (2026-02-27)
|
|
11
|
+
|
|
12
|
+
- Initial release.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yudai Takada
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# HarfBuzz Ruby
|
|
2
|
+
|
|
3
|
+
Ruby bindings for [HarfBuzz](https://harfbuzz.github.io/) text shaping engine.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Complete HarfBuzz API bindings using Ruby-FFI
|
|
8
|
+
- Two-layer architecture: low-level `HarfBuzz::C` (1:1 C binding) + high-level Ruby-idiomatic layer
|
|
9
|
+
- Safe memory management with borrow/own distinction
|
|
10
|
+
- Thread-safe immutable font objects
|
|
11
|
+
- Cross-platform support (macOS, Linux)
|
|
12
|
+
- RBS type signatures included
|
|
13
|
+
|
|
14
|
+
## Requirements
|
|
15
|
+
|
|
16
|
+
- Ruby 3.1+
|
|
17
|
+
- HarfBuzz C library installed on your system
|
|
18
|
+
- Supported platforms:
|
|
19
|
+
- macOS (Homebrew)
|
|
20
|
+
- Ubuntu / Debian
|
|
21
|
+
- Fedora / RHEL
|
|
22
|
+
- Alpine
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Install the HarfBuzz C library:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# macOS
|
|
30
|
+
brew install harfbuzz
|
|
31
|
+
|
|
32
|
+
# Ubuntu / Debian
|
|
33
|
+
sudo apt-get install libharfbuzz-dev libharfbuzz-subset0
|
|
34
|
+
|
|
35
|
+
# Fedora / RHEL
|
|
36
|
+
sudo dnf install harfbuzz-devel
|
|
37
|
+
|
|
38
|
+
# Alpine
|
|
39
|
+
apk add harfbuzz-dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Add to your Gemfile:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
gem 'harfbuzz-ruby'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then run:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
bundle install
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or install directly:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
gem install harfbuzz-ruby
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Custom Library Path
|
|
61
|
+
|
|
62
|
+
To use a custom build of HarfBuzz:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
export HARFBUZZ_LIB_PATH=/path/to/libharfbuzz.dylib
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
### Basic Setup
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
require "harfbuzz"
|
|
74
|
+
|
|
75
|
+
result = HarfBuzz.shape_text("Hello, World!", font_path: "/path/to/font.ttf")
|
|
76
|
+
result.each do |info, pos|
|
|
77
|
+
puts "glyph=#{info.glyph_id} x_advance=#{pos.x_advance}"
|
|
78
|
+
end
|
|
79
|
+
puts "Total advance: #{result.total_advance.first}"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Text Shaping Example
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
require "harfbuzz"
|
|
86
|
+
|
|
87
|
+
# Load font
|
|
88
|
+
blob = HarfBuzz::Blob.from_file!("/path/to/font.ttf")
|
|
89
|
+
face = HarfBuzz::Face.new(blob, 0)
|
|
90
|
+
font = HarfBuzz::Font.new(face)
|
|
91
|
+
|
|
92
|
+
# Create buffer and add text
|
|
93
|
+
buffer = HarfBuzz::Buffer.new
|
|
94
|
+
buffer.add_utf8("Hello, World!")
|
|
95
|
+
buffer.guess_segment_properties
|
|
96
|
+
|
|
97
|
+
# Shape
|
|
98
|
+
HarfBuzz.shape(font, buffer)
|
|
99
|
+
|
|
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}"
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Shaping with Features
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
features = [
|
|
111
|
+
HarfBuzz::Feature.from_string("liga"), # enable ligatures
|
|
112
|
+
HarfBuzz::Feature.from_string("-kern"), # disable kerning
|
|
113
|
+
]
|
|
114
|
+
HarfBuzz.shape(font, buffer, features)
|
|
115
|
+
|
|
116
|
+
# Or use a hash
|
|
117
|
+
features = HarfBuzz::Feature.from_hash(liga: true, kern: false, smcp: 2)
|
|
118
|
+
HarfBuzz.shape(font, buffer, features)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Variable Fonts
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
font = HarfBuzz::Font.new(face)
|
|
125
|
+
|
|
126
|
+
# Set weight=700 and width=75
|
|
127
|
+
variations = [
|
|
128
|
+
HarfBuzz::Variation.from_string("wght=700"),
|
|
129
|
+
HarfBuzz::Variation.from_string("wdth=75"),
|
|
130
|
+
]
|
|
131
|
+
font.variations = variations
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Glyph Outline Extraction
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
draw = HarfBuzz::DrawFuncs.new
|
|
138
|
+
|
|
139
|
+
path_commands = []
|
|
140
|
+
draw.on_move_to { |x, y, _| path_commands << "M #{x},#{y}" }
|
|
141
|
+
draw.on_line_to { |x, y, _| path_commands << "L #{x},#{y}" }
|
|
142
|
+
draw.on_quadratic_to { |cx, cy, x, y, _| path_commands << "Q #{cx},#{cy} #{x},#{y}" }
|
|
143
|
+
draw.on_cubic_to { |c1x, c1y, c2x, c2y, x, y, _|
|
|
144
|
+
path_commands << "C #{c1x},#{c1y} #{c2x},#{c2y} #{x},#{y}" }
|
|
145
|
+
draw.on_close_path { |_| path_commands << "Z" }
|
|
146
|
+
draw.make_immutable!
|
|
147
|
+
|
|
148
|
+
font.draw_glyph(36, draw)
|
|
149
|
+
puts path_commands.join(" ")
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Font Subsetting
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
input = HarfBuzz::Subset::Input.new
|
|
156
|
+
|
|
157
|
+
unicode_set = input.unicode_set
|
|
158
|
+
"Hello".each_codepoint { |cp| unicode_set.add(cp) }
|
|
159
|
+
|
|
160
|
+
subsetted_face = HarfBuzz::Subset.subset(face, input)
|
|
161
|
+
puts "Subsetted glyph count: #{subsetted_face.glyph_count}"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Examples
|
|
165
|
+
|
|
166
|
+
See the `examples/` directory for more complete examples:
|
|
167
|
+
|
|
168
|
+
- `basic_shaping.rb` - Basic text shaping
|
|
169
|
+
- `glyph_outlines.rb` - Glyph outline extraction
|
|
170
|
+
- `opentype_features.rb` - OpenType feature queries
|
|
171
|
+
- `variable_fonts.rb` - Variable font axis manipulation
|
|
172
|
+
- `render_svg.rb` - Render shaped text to SVG and open in browser
|
|
173
|
+
- `render_waterfall.rb` - Variable font weight waterfall (HTML + SVG)
|
|
174
|
+
|
|
175
|
+
Run an example:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
bundle exec ruby examples/basic_shaping.rb
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Rendering examples generate SVG/HTML and open them in the default browser:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
bundle exec ruby examples/render_svg.rb "Hello, HarfBuzz!"
|
|
185
|
+
bundle exec ruby examples/render_waterfall.rb /path/to/variable_font.ttf
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## API Overview
|
|
189
|
+
|
|
190
|
+
### Core Objects
|
|
191
|
+
|
|
192
|
+
| Class | Description |
|
|
193
|
+
|-------|-------------|
|
|
194
|
+
| `HarfBuzz::Blob` | Binary data container for font files |
|
|
195
|
+
| `HarfBuzz::Face` | Font face (typeface + index) |
|
|
196
|
+
| `HarfBuzz::Font` | Font instance with metrics and scale |
|
|
197
|
+
| `HarfBuzz::Buffer` | Text buffer for shaping input/output |
|
|
198
|
+
|
|
199
|
+
### Shaping
|
|
200
|
+
|
|
201
|
+
| Class | Description |
|
|
202
|
+
|-------|-------------|
|
|
203
|
+
| `HarfBuzz::Feature` | OpenType feature toggle |
|
|
204
|
+
| `HarfBuzz::Variation` | Variable font axis setting |
|
|
205
|
+
| `HarfBuzz::ShapePlan` | Reusable shaping plan |
|
|
206
|
+
| `HarfBuzz::ShapingResult` | Shaped text result with glyph info |
|
|
207
|
+
| `HarfBuzz::GlyphInfo` | Glyph ID and cluster info |
|
|
208
|
+
| `HarfBuzz::GlyphPosition` | Glyph advance and offset |
|
|
209
|
+
|
|
210
|
+
### Drawing & Color
|
|
211
|
+
|
|
212
|
+
| Class | Description |
|
|
213
|
+
|-------|-------------|
|
|
214
|
+
| `HarfBuzz::DrawFuncs` | Glyph outline extraction callbacks |
|
|
215
|
+
| `HarfBuzz::PaintFuncs` | Color font paint callbacks |
|
|
216
|
+
|
|
217
|
+
### OpenType
|
|
218
|
+
|
|
219
|
+
| Class | Description |
|
|
220
|
+
|-------|-------------|
|
|
221
|
+
| `HarfBuzz::OT::Layout` | GSUB/GPOS script, language, and feature queries |
|
|
222
|
+
| `HarfBuzz::OT::Var` | Variable font axis and instance queries |
|
|
223
|
+
| `HarfBuzz::OT::Metrics` | Font metrics (x-height, cap-height, etc.) |
|
|
224
|
+
| `HarfBuzz::OT::Name` | Name table access |
|
|
225
|
+
| `HarfBuzz::OT::Color` | Color palette and COLR queries |
|
|
226
|
+
| `HarfBuzz::OT::Math` | Math table queries |
|
|
227
|
+
| `HarfBuzz::OT::Meta` | Metadata queries |
|
|
228
|
+
|
|
229
|
+
### Subsetting
|
|
230
|
+
|
|
231
|
+
| Class | Description |
|
|
232
|
+
|-------|-------------|
|
|
233
|
+
| `HarfBuzz::Subset` | Font subsetting API |
|
|
234
|
+
| `HarfBuzz::Set` | Integer set for codepoints/glyphs |
|
|
235
|
+
| `HarfBuzz::Map` | Integer-to-integer mapping |
|
|
236
|
+
|
|
237
|
+
## Development
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
git clone https://github.com/ydah/harfbuzz.git
|
|
241
|
+
cd harfbuzz
|
|
242
|
+
bundle install
|
|
243
|
+
bundle exec rake spec
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Contributing
|
|
247
|
+
|
|
248
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ydah/harfbuzz.
|
|
249
|
+
|
|
250
|
+
## License
|
|
251
|
+
|
|
252
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
253
|
+
|
|
254
|
+
## References
|
|
255
|
+
|
|
256
|
+
- [HarfBuzz Documentation](https://harfbuzz.github.io/)
|
|
257
|
+
- [HarfBuzz GitHub](https://github.com/harfbuzz/harfbuzz)
|
|
258
|
+
- [OpenType Specification](https://learn.microsoft.com/en-us/typography/opentype/spec/)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# benchmark/shaping_bench.rb — HarfBuzz Ruby FFI shaping benchmarks
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# bundle exec ruby benchmark/shaping_bench.rb [path/to/font.ttf]
|
|
7
|
+
#
|
|
8
|
+
# Requires benchmark-ips gem: gem install benchmark-ips
|
|
9
|
+
|
|
10
|
+
require "benchmark/ips"
|
|
11
|
+
require "harfbuzz"
|
|
12
|
+
|
|
13
|
+
font_path = ARGV[0] ||
|
|
14
|
+
if File.exist?("/System/Library/Fonts/Helvetica.ttc")
|
|
15
|
+
"/System/Library/Fonts/Helvetica.ttc"
|
|
16
|
+
elsif File.exist?("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
|
|
17
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
|
|
18
|
+
else
|
|
19
|
+
raise "No test font found. Pass font path as argument: ruby benchmark/shaping_bench.rb /path/to/font.ttf"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
puts "HarfBuzz version: #{HarfBuzz.version_string}"
|
|
23
|
+
puts "Font: #{font_path}"
|
|
24
|
+
puts
|
|
25
|
+
|
|
26
|
+
blob = HarfBuzz::Blob.from_file!(font_path)
|
|
27
|
+
face = HarfBuzz::Face.new(blob, 0)
|
|
28
|
+
font = HarfBuzz::Font.new(face)
|
|
29
|
+
font.make_immutable!
|
|
30
|
+
|
|
31
|
+
Benchmark.ips do |x|
|
|
32
|
+
x.config(time: 5, warmup: 2)
|
|
33
|
+
|
|
34
|
+
x.report("shape Latin 10 chars") do
|
|
35
|
+
buf = HarfBuzz::Buffer.new
|
|
36
|
+
buf.add_utf8("Hello Word")
|
|
37
|
+
buf.guess_segment_properties
|
|
38
|
+
HarfBuzz.shape(font, buf)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
x.report("shape Latin 100 chars") do
|
|
42
|
+
buf = HarfBuzz::Buffer.new
|
|
43
|
+
buf.add_utf8("a" * 100)
|
|
44
|
+
buf.guess_segment_properties
|
|
45
|
+
HarfBuzz.shape(font, buf)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
x.report("shape with buffer reuse") do |times|
|
|
49
|
+
buf = HarfBuzz::Buffer.new
|
|
50
|
+
times.times do
|
|
51
|
+
buf.clear
|
|
52
|
+
buf.add_utf8("Hello Word")
|
|
53
|
+
buf.guess_segment_properties
|
|
54
|
+
HarfBuzz.shape(font, buf)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
x.report("create + destroy buffer") do
|
|
59
|
+
HarfBuzz::Buffer.new
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
x.report("glyph outline extraction") do
|
|
63
|
+
draw = HarfBuzz::DrawFuncs.new
|
|
64
|
+
draw.on_move_to { |_x, _y, _| }
|
|
65
|
+
draw.on_line_to { |_x, _y, _| }
|
|
66
|
+
draw.on_cubic_to { |*_args| }
|
|
67
|
+
draw.on_close_path { |_| }
|
|
68
|
+
draw.make_immutable!
|
|
69
|
+
font.draw_glyph(36, draw)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
x.report("shape_text one-liner") do
|
|
73
|
+
HarfBuzz.shape_text("Hello", font_path: font_path)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
x.compare!
|
|
77
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# examples/basic_shaping.rb — Basic text shaping with HarfBuzz
|
|
4
|
+
#
|
|
5
|
+
# Usage: ruby examples/basic_shaping.rb [path/to/font.ttf]
|
|
6
|
+
|
|
7
|
+
require "harfbuzz"
|
|
8
|
+
|
|
9
|
+
font_path = ARGV[0] ||
|
|
10
|
+
if File.exist?("/System/Library/Fonts/Helvetica.ttc")
|
|
11
|
+
"/System/Library/Fonts/Helvetica.ttc"
|
|
12
|
+
elsif File.exist?("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
|
|
13
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
|
|
14
|
+
else
|
|
15
|
+
raise "No font found. Pass font path as argument."
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
puts "=== Basic Shaping ==="
|
|
19
|
+
puts "HarfBuzz #{HarfBuzz.version_string}"
|
|
20
|
+
puts
|
|
21
|
+
|
|
22
|
+
# --- One-liner API ---
|
|
23
|
+
puts "--- shape_text (one-liner) ---"
|
|
24
|
+
result = HarfBuzz.shape_text("Hello, World!", font_path: font_path)
|
|
25
|
+
puts "Glyphs: #{result.length}"
|
|
26
|
+
result.each do |info, pos|
|
|
27
|
+
puts " glyph=#{info.glyph_id} cluster=#{info.cluster} x_advance=#{pos.x_advance}"
|
|
28
|
+
end
|
|
29
|
+
puts "Total X advance: #{result.total_advance.first}"
|
|
30
|
+
puts
|
|
31
|
+
|
|
32
|
+
# --- Manual API ---
|
|
33
|
+
puts "--- Manual Buffer + Font API ---"
|
|
34
|
+
blob = HarfBuzz::Blob.from_file!(font_path)
|
|
35
|
+
face = HarfBuzz::Face.new(blob, 0)
|
|
36
|
+
font = HarfBuzz::Font.new(face)
|
|
37
|
+
buffer = HarfBuzz::Buffer.new
|
|
38
|
+
|
|
39
|
+
buffer.add_utf8("Ruby")
|
|
40
|
+
buffer.guess_segment_properties
|
|
41
|
+
HarfBuzz.shape(font, buffer)
|
|
42
|
+
|
|
43
|
+
puts "Shaped '#{buffer.serialize}'"
|
|
44
|
+
puts "Glyph count: #{buffer.length}"
|
|
45
|
+
buffer.glyph_infos.zip(buffer.glyph_positions).each do |info, pos|
|
|
46
|
+
puts " glyph_id=#{info.glyph_id} x_advance=#{pos.x_advance} y_advance=#{pos.y_advance}"
|
|
47
|
+
end
|
|
48
|
+
puts
|
|
49
|
+
|
|
50
|
+
# --- Buffer reuse pattern ---
|
|
51
|
+
puts "--- Buffer reuse ---"
|
|
52
|
+
texts = ["Hello", "World", "Ruby", "HarfBuzz"]
|
|
53
|
+
texts.each do |text|
|
|
54
|
+
buffer.reset
|
|
55
|
+
buffer.add_utf8(text)
|
|
56
|
+
buffer.guess_segment_properties
|
|
57
|
+
HarfBuzz.shape(font, buffer)
|
|
58
|
+
total_advance = buffer.glyph_positions.sum(&:x_advance)
|
|
59
|
+
puts " '#{text}': #{buffer.length} glyphs, total_advance=#{total_advance}"
|
|
60
|
+
end
|
|
61
|
+
puts
|
|
62
|
+
|
|
63
|
+
# --- RTL shaping ---
|
|
64
|
+
puts "--- RTL (Arabic) ---"
|
|
65
|
+
arabic_result = HarfBuzz.shape_text("مرحبا", font_path: font_path, direction: :rtl)
|
|
66
|
+
puts "Arabic glyphs: #{arabic_result.length}"
|
|
67
|
+
puts "Direction detected: #{HarfBuzz::Buffer.new.tap { |b| b.add_utf8("مرحبا"); b.guess_segment_properties }.direction}"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# examples/glyph_outlines.rb — Extract glyph outlines using DrawFuncs
|
|
4
|
+
#
|
|
5
|
+
# Usage: ruby examples/glyph_outlines.rb [path/to/font.ttf]
|
|
6
|
+
|
|
7
|
+
require "harfbuzz"
|
|
8
|
+
|
|
9
|
+
font_path = ARGV[0] ||
|
|
10
|
+
if File.exist?("/System/Library/Fonts/Helvetica.ttc")
|
|
11
|
+
"/System/Library/Fonts/Helvetica.ttc"
|
|
12
|
+
elsif File.exist?("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
|
|
13
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
|
|
14
|
+
else
|
|
15
|
+
raise "No font found. Pass font path as argument."
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
puts "=== Glyph Outline Extraction ==="
|
|
19
|
+
puts
|
|
20
|
+
|
|
21
|
+
blob = HarfBuzz::Blob.from_file!(font_path)
|
|
22
|
+
face = HarfBuzz::Face.new(blob, 0)
|
|
23
|
+
font = HarfBuzz::Font.new(face)
|
|
24
|
+
|
|
25
|
+
# --- Set up DrawFuncs ---
|
|
26
|
+
draw = HarfBuzz::DrawFuncs.new
|
|
27
|
+
|
|
28
|
+
commands = []
|
|
29
|
+
|
|
30
|
+
draw.on_move_to do |x, y, _draw_state|
|
|
31
|
+
commands << "M #{x.round(2)},#{y.round(2)}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
draw.on_line_to do |x, y, _draw_state|
|
|
35
|
+
commands << "L #{x.round(2)},#{y.round(2)}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
draw.on_quadratic_to do |cx, cy, x, y, _draw_state|
|
|
39
|
+
commands << "Q #{cx.round(2)},#{cy.round(2)} #{x.round(2)},#{y.round(2)}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
draw.on_cubic_to do |c1x, c1y, c2x, c2y, x, y, _draw_state|
|
|
43
|
+
commands << "C #{c1x.round(2)},#{c1y.round(2)} #{c2x.round(2)},#{c2y.round(2)} #{x.round(2)},#{y.round(2)}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
draw.on_close_path do |_draw_state|
|
|
47
|
+
commands << "Z"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
draw.make_immutable!
|
|
51
|
+
|
|
52
|
+
# --- Shape text and get glyph IDs ---
|
|
53
|
+
buffer = HarfBuzz::Buffer.new
|
|
54
|
+
buffer.add_utf8("AB")
|
|
55
|
+
buffer.guess_segment_properties
|
|
56
|
+
HarfBuzz.shape(font, buffer)
|
|
57
|
+
|
|
58
|
+
# --- Extract outline for each glyph ---
|
|
59
|
+
buffer.glyph_infos.each do |info|
|
|
60
|
+
commands.clear
|
|
61
|
+
font.draw_glyph(info.glyph_id, draw)
|
|
62
|
+
puts "Glyph #{info.glyph_id} (cluster #{info.cluster}):"
|
|
63
|
+
if commands.empty?
|
|
64
|
+
puts " (no outline — space or missing)"
|
|
65
|
+
else
|
|
66
|
+
puts " SVG path: #{commands.join(" ")}"
|
|
67
|
+
puts " Commands: #{commands.length}"
|
|
68
|
+
end
|
|
69
|
+
puts
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# --- Glyph extents ---
|
|
73
|
+
puts "--- Glyph Extents ---"
|
|
74
|
+
buffer.glyph_infos.each do |info|
|
|
75
|
+
extents = font.glyph_extents(info.glyph_id)
|
|
76
|
+
if extents
|
|
77
|
+
puts "Glyph #{info.glyph_id}: x_bearing=#{extents[:x_bearing]} y_bearing=#{extents[:y_bearing]} width=#{extents[:width]} height=#{extents[:height]}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# examples/opentype_features.rb — OpenType feature queries and shaping with features
|
|
4
|
+
#
|
|
5
|
+
# Usage: ruby examples/opentype_features.rb [path/to/font.ttf]
|
|
6
|
+
|
|
7
|
+
require "harfbuzz"
|
|
8
|
+
|
|
9
|
+
font_path = ARGV[0] ||
|
|
10
|
+
if File.exist?("/System/Library/Fonts/Helvetica.ttc")
|
|
11
|
+
"/System/Library/Fonts/Helvetica.ttc"
|
|
12
|
+
elsif File.exist?("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
|
|
13
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
|
|
14
|
+
else
|
|
15
|
+
raise "No font found. Pass font path as argument."
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
puts "=== OpenType Features ==="
|
|
19
|
+
puts
|
|
20
|
+
|
|
21
|
+
blob = HarfBuzz::Blob.from_file!(font_path)
|
|
22
|
+
face = HarfBuzz::Face.new(blob, 0)
|
|
23
|
+
font = HarfBuzz::Font.new(face)
|
|
24
|
+
|
|
25
|
+
# --- List GSUB scripts ---
|
|
26
|
+
puts "--- GSUB Scripts ---"
|
|
27
|
+
begin
|
|
28
|
+
scripts = HarfBuzz::OT::Layout.script_tags(face, :gsub)
|
|
29
|
+
puts "Scripts: #{scripts.map { |t| HarfBuzz.tag_to_s(t) }.inspect}"
|
|
30
|
+
rescue => e
|
|
31
|
+
puts " (#{e.message})"
|
|
32
|
+
end
|
|
33
|
+
puts
|
|
34
|
+
|
|
35
|
+
# --- List GSUB features for default script ---
|
|
36
|
+
puts "--- GSUB Features (first script/language) ---"
|
|
37
|
+
begin
|
|
38
|
+
features = HarfBuzz::OT::Layout.feature_tags(face, :gsub, 0, 0)
|
|
39
|
+
puts "Features: #{features.map { |t| HarfBuzz.tag_to_s(t) }.first(10).inspect}"
|
|
40
|
+
rescue => e
|
|
41
|
+
puts " (#{e.message})"
|
|
42
|
+
end
|
|
43
|
+
puts
|
|
44
|
+
|
|
45
|
+
# --- Shaping with explicit features ---
|
|
46
|
+
puts "--- Shaping with Features ---"
|
|
47
|
+
font2 = HarfBuzz::Font.new(face)
|
|
48
|
+
|
|
49
|
+
buf_with_liga = HarfBuzz::Buffer.new
|
|
50
|
+
buf_with_liga.add_utf8("fi")
|
|
51
|
+
buf_with_liga.guess_segment_properties
|
|
52
|
+
HarfBuzz.shape(font2, buf_with_liga, [HarfBuzz::Feature.from_string("liga")])
|
|
53
|
+
puts "With liga: #{buf_with_liga.length} glyph(s)"
|
|
54
|
+
|
|
55
|
+
buf_no_liga = HarfBuzz::Buffer.new
|
|
56
|
+
buf_no_liga.add_utf8("fi")
|
|
57
|
+
buf_no_liga.guess_segment_properties
|
|
58
|
+
HarfBuzz.shape(font2, buf_no_liga, [HarfBuzz::Feature.from_string("-liga")])
|
|
59
|
+
puts "Without liga: #{buf_no_liga.length} glyph(s)"
|
|
60
|
+
puts
|
|
61
|
+
|
|
62
|
+
# --- Feature from hash ---
|
|
63
|
+
puts "--- Feature from Hash ---"
|
|
64
|
+
features = HarfBuzz::Feature.from_hash(liga: true, kern: false)
|
|
65
|
+
puts "Features: #{features.map(&:to_s).inspect}"
|
|
66
|
+
puts
|
|
67
|
+
|
|
68
|
+
# --- Glyph class query ---
|
|
69
|
+
puts "--- Glyph Classes ---"
|
|
70
|
+
begin
|
|
71
|
+
has_classes = HarfBuzz::OT::Layout.has_glyph_classes?(face)
|
|
72
|
+
puts "Has glyph classes: #{has_classes}"
|
|
73
|
+
if has_classes
|
|
74
|
+
klass = HarfBuzz::OT::Layout.glyph_class(face, 65) # 'A'
|
|
75
|
+
puts " Glyph class of glyph 65: #{klass}"
|
|
76
|
+
end
|
|
77
|
+
rescue => e
|
|
78
|
+
puts " (#{e.message})"
|
|
79
|
+
end
|
|
80
|
+
puts
|
|
81
|
+
|
|
82
|
+
# --- OT Metrics ---
|
|
83
|
+
puts "--- OT Metrics ---"
|
|
84
|
+
begin
|
|
85
|
+
x_height = HarfBuzz::OT::Metrics.x_height(font)
|
|
86
|
+
puts "x-height: #{x_height}"
|
|
87
|
+
cap_height = HarfBuzz::OT::Metrics.cap_height(font)
|
|
88
|
+
puts "cap-height: #{cap_height}"
|
|
89
|
+
rescue => e
|
|
90
|
+
puts " (#{e.message})"
|
|
91
|
+
end
|