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
|
@@ -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
|