fontisan 0.1.0 → 0.2.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/.rubocop_todo.yml +529 -65
- data/Gemfile +1 -0
- data/LICENSE +5 -1
- data/README.adoc +1301 -275
- data/Rakefile +27 -2
- data/benchmark/variation_quick_bench.rb +47 -0
- data/docs/EXTRACT_TTC_MIGRATION.md +549 -0
- data/fontisan.gemspec +4 -1
- data/lib/fontisan/binary/base_record.rb +22 -1
- data/lib/fontisan/cli.rb +309 -0
- data/lib/fontisan/collection/builder.rb +260 -0
- data/lib/fontisan/collection/offset_calculator.rb +227 -0
- data/lib/fontisan/collection/table_analyzer.rb +204 -0
- data/lib/fontisan/collection/table_deduplicator.rb +241 -0
- data/lib/fontisan/collection/writer.rb +306 -0
- data/lib/fontisan/commands/base_command.rb +8 -1
- data/lib/fontisan/commands/convert_command.rb +291 -0
- data/lib/fontisan/commands/export_command.rb +161 -0
- data/lib/fontisan/commands/info_command.rb +40 -6
- data/lib/fontisan/commands/instance_command.rb +295 -0
- data/lib/fontisan/commands/ls_command.rb +113 -0
- data/lib/fontisan/commands/pack_command.rb +241 -0
- data/lib/fontisan/commands/subset_command.rb +245 -0
- data/lib/fontisan/commands/unpack_command.rb +338 -0
- data/lib/fontisan/commands/validate_command.rb +178 -0
- data/lib/fontisan/commands/variable_command.rb +30 -1
- data/lib/fontisan/config/collection_settings.yml +56 -0
- data/lib/fontisan/config/conversion_matrix.yml +212 -0
- data/lib/fontisan/config/export_settings.yml +66 -0
- data/lib/fontisan/config/subset_profiles.yml +100 -0
- data/lib/fontisan/config/svg_settings.yml +60 -0
- data/lib/fontisan/config/validation_rules.yml +149 -0
- data/lib/fontisan/config/variable_settings.yml +99 -0
- data/lib/fontisan/config/woff2_settings.yml +77 -0
- data/lib/fontisan/constants.rb +69 -0
- data/lib/fontisan/converters/conversion_strategy.rb +96 -0
- data/lib/fontisan/converters/format_converter.rb +259 -0
- data/lib/fontisan/converters/outline_converter.rb +936 -0
- data/lib/fontisan/converters/svg_generator.rb +244 -0
- data/lib/fontisan/converters/table_copier.rb +117 -0
- data/lib/fontisan/converters/woff2_encoder.rb +416 -0
- data/lib/fontisan/converters/woff_writer.rb +391 -0
- data/lib/fontisan/error.rb +203 -0
- data/lib/fontisan/export/exporter.rb +262 -0
- data/lib/fontisan/export/table_serializer.rb +255 -0
- data/lib/fontisan/export/transformers/font_to_ttx.rb +172 -0
- data/lib/fontisan/export/transformers/head_transformer.rb +96 -0
- data/lib/fontisan/export/transformers/hhea_transformer.rb +59 -0
- data/lib/fontisan/export/transformers/maxp_transformer.rb +63 -0
- data/lib/fontisan/export/transformers/name_transformer.rb +63 -0
- data/lib/fontisan/export/transformers/os2_transformer.rb +121 -0
- data/lib/fontisan/export/transformers/post_transformer.rb +51 -0
- data/lib/fontisan/export/ttx_generator.rb +527 -0
- data/lib/fontisan/export/ttx_parser.rb +300 -0
- data/lib/fontisan/font_loader.rb +121 -12
- data/lib/fontisan/font_writer.rb +301 -0
- data/lib/fontisan/formatters/text_formatter.rb +102 -0
- data/lib/fontisan/glyph_accessor.rb +503 -0
- data/lib/fontisan/hints/hint_converter.rb +177 -0
- data/lib/fontisan/hints/postscript_hint_applier.rb +185 -0
- data/lib/fontisan/hints/postscript_hint_extractor.rb +254 -0
- data/lib/fontisan/hints/truetype_hint_applier.rb +71 -0
- data/lib/fontisan/hints/truetype_hint_extractor.rb +162 -0
- data/lib/fontisan/loading_modes.rb +113 -0
- data/lib/fontisan/metrics_calculator.rb +277 -0
- data/lib/fontisan/models/collection_font_summary.rb +52 -0
- data/lib/fontisan/models/collection_info.rb +76 -0
- data/lib/fontisan/models/collection_list_info.rb +37 -0
- data/lib/fontisan/models/font_export.rb +158 -0
- data/lib/fontisan/models/font_summary.rb +48 -0
- data/lib/fontisan/models/glyph_outline.rb +343 -0
- data/lib/fontisan/models/hint.rb +233 -0
- data/lib/fontisan/models/outline.rb +664 -0
- data/lib/fontisan/models/table_sharing_info.rb +40 -0
- data/lib/fontisan/models/ttx/glyph_order.rb +31 -0
- data/lib/fontisan/models/ttx/tables/binary_table.rb +67 -0
- data/lib/fontisan/models/ttx/tables/head_table.rb +74 -0
- data/lib/fontisan/models/ttx/tables/hhea_table.rb +74 -0
- data/lib/fontisan/models/ttx/tables/maxp_table.rb +55 -0
- data/lib/fontisan/models/ttx/tables/name_table.rb +45 -0
- data/lib/fontisan/models/ttx/tables/os2_table.rb +157 -0
- data/lib/fontisan/models/ttx/tables/post_table.rb +50 -0
- data/lib/fontisan/models/ttx/ttfont.rb +49 -0
- data/lib/fontisan/models/validation_report.rb +203 -0
- data/lib/fontisan/open_type_collection.rb +156 -2
- data/lib/fontisan/open_type_font.rb +296 -10
- data/lib/fontisan/optimizers/charstring_rewriter.rb +161 -0
- data/lib/fontisan/optimizers/pattern_analyzer.rb +308 -0
- data/lib/fontisan/optimizers/stack_tracker.rb +246 -0
- data/lib/fontisan/optimizers/subroutine_builder.rb +134 -0
- data/lib/fontisan/optimizers/subroutine_generator.rb +207 -0
- data/lib/fontisan/optimizers/subroutine_optimizer.rb +107 -0
- data/lib/fontisan/outline_extractor.rb +423 -0
- data/lib/fontisan/subset/builder.rb +268 -0
- data/lib/fontisan/subset/glyph_mapping.rb +215 -0
- data/lib/fontisan/subset/options.rb +142 -0
- data/lib/fontisan/subset/profile.rb +152 -0
- data/lib/fontisan/subset/table_subsetter.rb +461 -0
- data/lib/fontisan/svg/font_face_generator.rb +278 -0
- data/lib/fontisan/svg/font_generator.rb +264 -0
- data/lib/fontisan/svg/glyph_generator.rb +168 -0
- data/lib/fontisan/svg/view_box_calculator.rb +137 -0
- data/lib/fontisan/tables/cff/cff_glyph.rb +176 -0
- data/lib/fontisan/tables/cff/charset.rb +282 -0
- data/lib/fontisan/tables/cff/charstring.rb +905 -0
- data/lib/fontisan/tables/cff/charstring_builder.rb +322 -0
- data/lib/fontisan/tables/cff/charstrings_index.rb +162 -0
- data/lib/fontisan/tables/cff/dict.rb +351 -0
- data/lib/fontisan/tables/cff/dict_builder.rb +242 -0
- data/lib/fontisan/tables/cff/encoding.rb +274 -0
- data/lib/fontisan/tables/cff/header.rb +102 -0
- data/lib/fontisan/tables/cff/index.rb +237 -0
- data/lib/fontisan/tables/cff/index_builder.rb +170 -0
- data/lib/fontisan/tables/cff/private_dict.rb +284 -0
- data/lib/fontisan/tables/cff/top_dict.rb +236 -0
- data/lib/fontisan/tables/cff.rb +487 -0
- data/lib/fontisan/tables/cff2/blend_operator.rb +240 -0
- data/lib/fontisan/tables/cff2/charstring_parser.rb +591 -0
- data/lib/fontisan/tables/cff2/operand_stack.rb +232 -0
- data/lib/fontisan/tables/cff2.rb +341 -0
- data/lib/fontisan/tables/cvar.rb +242 -0
- data/lib/fontisan/tables/fvar.rb +2 -2
- data/lib/fontisan/tables/glyf/compound_glyph.rb +483 -0
- data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +136 -0
- data/lib/fontisan/tables/glyf/curve_converter.rb +343 -0
- data/lib/fontisan/tables/glyf/glyph_builder.rb +450 -0
- data/lib/fontisan/tables/glyf/simple_glyph.rb +382 -0
- data/lib/fontisan/tables/glyf.rb +235 -0
- data/lib/fontisan/tables/gvar.rb +270 -0
- data/lib/fontisan/tables/hhea.rb +124 -0
- data/lib/fontisan/tables/hmtx.rb +287 -0
- data/lib/fontisan/tables/hvar.rb +191 -0
- data/lib/fontisan/tables/loca.rb +322 -0
- data/lib/fontisan/tables/maxp.rb +192 -0
- data/lib/fontisan/tables/mvar.rb +185 -0
- data/lib/fontisan/tables/name.rb +99 -30
- data/lib/fontisan/tables/variation_common.rb +346 -0
- data/lib/fontisan/tables/vvar.rb +234 -0
- data/lib/fontisan/true_type_collection.rb +156 -2
- data/lib/fontisan/true_type_font.rb +297 -11
- data/lib/fontisan/utilities/brotli_wrapper.rb +159 -0
- data/lib/fontisan/utilities/checksum_calculator.rb +18 -0
- data/lib/fontisan/utils/thread_pool.rb +134 -0
- data/lib/fontisan/validation/checksum_validator.rb +170 -0
- data/lib/fontisan/validation/consistency_validator.rb +197 -0
- data/lib/fontisan/validation/structure_validator.rb +198 -0
- data/lib/fontisan/validation/table_validator.rb +158 -0
- data/lib/fontisan/validation/validator.rb +152 -0
- data/lib/fontisan/variable/axis_normalizer.rb +215 -0
- data/lib/fontisan/variable/delta_applicator.rb +313 -0
- data/lib/fontisan/variable/glyph_delta_processor.rb +218 -0
- data/lib/fontisan/variable/instancer.rb +344 -0
- data/lib/fontisan/variable/metric_delta_processor.rb +282 -0
- data/lib/fontisan/variable/region_matcher.rb +208 -0
- data/lib/fontisan/variable/static_font_builder.rb +213 -0
- data/lib/fontisan/variable/table_updater.rb +219 -0
- data/lib/fontisan/variation/blend_applier.rb +199 -0
- data/lib/fontisan/variation/cache.rb +298 -0
- data/lib/fontisan/variation/cache_key_builder.rb +162 -0
- data/lib/fontisan/variation/converter.rb +268 -0
- data/lib/fontisan/variation/data_extractor.rb +86 -0
- data/lib/fontisan/variation/delta_applier.rb +266 -0
- data/lib/fontisan/variation/delta_parser.rb +228 -0
- data/lib/fontisan/variation/inspector.rb +275 -0
- data/lib/fontisan/variation/instance_generator.rb +273 -0
- data/lib/fontisan/variation/interpolator.rb +231 -0
- data/lib/fontisan/variation/metrics_adjuster.rb +318 -0
- data/lib/fontisan/variation/optimizer.rb +418 -0
- data/lib/fontisan/variation/parallel_generator.rb +150 -0
- data/lib/fontisan/variation/region_matcher.rb +221 -0
- data/lib/fontisan/variation/subsetter.rb +463 -0
- data/lib/fontisan/variation/table_accessor.rb +105 -0
- data/lib/fontisan/variation/validator.rb +345 -0
- data/lib/fontisan/variation/variation_context.rb +211 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/directory.rb +257 -0
- data/lib/fontisan/woff2/header.rb +101 -0
- data/lib/fontisan/woff2/table_transformer.rb +163 -0
- data/lib/fontisan/woff2_font.rb +712 -0
- data/lib/fontisan/woff_font.rb +483 -0
- data/lib/fontisan.rb +120 -0
- data/scripts/compare_stack_aware.rb +187 -0
- data/scripts/measure_optimization.rb +141 -0
- metadata +205 -4
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "interpolator"
|
|
4
|
+
require_relative "region_matcher"
|
|
5
|
+
require_relative "table_accessor"
|
|
6
|
+
|
|
7
|
+
module Fontisan
|
|
8
|
+
module Variation
|
|
9
|
+
# Applies metrics variation deltas to font metrics tables
|
|
10
|
+
#
|
|
11
|
+
# This class handles applying variation deltas from HVAR, VVAR, and MVAR
|
|
12
|
+
# tables to the corresponding metrics tables (hmtx, vmtx, head, hhea, etc.).
|
|
13
|
+
#
|
|
14
|
+
# Process:
|
|
15
|
+
# 1. Parse ItemVariationStore from HVAR/VVAR/MVAR
|
|
16
|
+
# 2. Calculate scalars for current coordinates using regions
|
|
17
|
+
# 3. Apply deltas to base metrics
|
|
18
|
+
# 4. Update metrics tables with adjusted values
|
|
19
|
+
#
|
|
20
|
+
# @example Applying HVAR deltas
|
|
21
|
+
# adjuster = MetricsAdjuster.new(font, interpolator)
|
|
22
|
+
# adjuster.apply_hvar_deltas({ "wght" => 700.0 })
|
|
23
|
+
class MetricsAdjuster
|
|
24
|
+
include TableAccessor
|
|
25
|
+
|
|
26
|
+
# @return [TrueTypeFont, OpenTypeFont] Font instance
|
|
27
|
+
attr_reader :font
|
|
28
|
+
|
|
29
|
+
# @return [Interpolator] Coordinate interpolator
|
|
30
|
+
attr_reader :interpolator
|
|
31
|
+
|
|
32
|
+
# Initialize metrics adjuster
|
|
33
|
+
#
|
|
34
|
+
# @param font [TrueTypeFont, OpenTypeFont] Font instance
|
|
35
|
+
# @param interpolator [Interpolator] Coordinate interpolator
|
|
36
|
+
def initialize(font, interpolator)
|
|
37
|
+
@font = font
|
|
38
|
+
@interpolator = interpolator
|
|
39
|
+
@variation_tables = {}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Apply HVAR deltas to horizontal metrics
|
|
43
|
+
#
|
|
44
|
+
# @param coordinates [Hash<String, Float>] Design space coordinates
|
|
45
|
+
# @return [Boolean] True if deltas were applied
|
|
46
|
+
def apply_hvar_deltas(coordinates)
|
|
47
|
+
return false unless has_variation_table?("HVAR")
|
|
48
|
+
return false unless has_variation_table?("hmtx")
|
|
49
|
+
|
|
50
|
+
hvar = variation_table("HVAR")
|
|
51
|
+
return false unless hvar&.item_variation_store
|
|
52
|
+
|
|
53
|
+
# Parse region list and calculate scalars
|
|
54
|
+
regions = extract_regions_from_store(hvar.item_variation_store)
|
|
55
|
+
return false if regions.empty?
|
|
56
|
+
|
|
57
|
+
scalars = @interpolator.calculate_scalars(coordinates, regions)
|
|
58
|
+
|
|
59
|
+
# Get hmtx table
|
|
60
|
+
hmtx = variation_table("hmtx")
|
|
61
|
+
return false unless hmtx&.parsed?
|
|
62
|
+
|
|
63
|
+
# Get glyph count
|
|
64
|
+
maxp = variation_table("maxp")
|
|
65
|
+
glyph_count = maxp ? maxp.num_glyphs : 0
|
|
66
|
+
return false if glyph_count.zero?
|
|
67
|
+
|
|
68
|
+
# Apply deltas to each glyph
|
|
69
|
+
adjusted_metrics = apply_horizontal_metrics_deltas(
|
|
70
|
+
hvar, hmtx, glyph_count, scalars
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Rebuild hmtx table with adjusted metrics
|
|
74
|
+
rebuild_hmtx_table(adjusted_metrics)
|
|
75
|
+
|
|
76
|
+
true
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Apply VVAR deltas to vertical metrics
|
|
80
|
+
#
|
|
81
|
+
# @param coordinates [Hash<String, Float>] Design space coordinates
|
|
82
|
+
# @return [Boolean] True if deltas were applied
|
|
83
|
+
def apply_vvar_deltas(coordinates)
|
|
84
|
+
return false unless has_variation_table?("VVAR")
|
|
85
|
+
return false unless has_variation_table?("vmtx")
|
|
86
|
+
|
|
87
|
+
vvar = variation_table("VVAR")
|
|
88
|
+
return false unless vvar&.item_variation_store
|
|
89
|
+
|
|
90
|
+
# Parse region list and calculate scalars
|
|
91
|
+
regions = extract_regions_from_store(vvar.item_variation_store)
|
|
92
|
+
return false if regions.empty?
|
|
93
|
+
|
|
94
|
+
@interpolator.calculate_scalars(coordinates, regions)
|
|
95
|
+
|
|
96
|
+
# Apply deltas to vmtx
|
|
97
|
+
# Similar to HVAR but for vertical metrics
|
|
98
|
+
# Placeholder for full implementation
|
|
99
|
+
|
|
100
|
+
true
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Apply MVAR deltas to font-wide metrics
|
|
104
|
+
#
|
|
105
|
+
# @param coordinates [Hash<String, Float>] Design space coordinates
|
|
106
|
+
# @return [Boolean] True if deltas were applied
|
|
107
|
+
def apply_mvar_deltas(coordinates)
|
|
108
|
+
return false unless has_variation_table?("MVAR")
|
|
109
|
+
|
|
110
|
+
mvar = variation_table("MVAR")
|
|
111
|
+
return false unless mvar&.item_variation_store
|
|
112
|
+
|
|
113
|
+
# Parse region list and calculate scalars
|
|
114
|
+
regions = extract_regions_from_store(mvar.item_variation_store)
|
|
115
|
+
return false if regions.empty?
|
|
116
|
+
|
|
117
|
+
scalars = @interpolator.calculate_scalars(coordinates, regions)
|
|
118
|
+
|
|
119
|
+
# Apply deltas to each metric tag
|
|
120
|
+
apply_font_wide_metrics_deltas(mvar, scalars)
|
|
121
|
+
|
|
122
|
+
true
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
# Extract regions from ItemVariationStore
|
|
128
|
+
#
|
|
129
|
+
# @param store [VariationCommon::ItemVariationStore] Variation store
|
|
130
|
+
# @return [Array<Hash>] Array of region definitions
|
|
131
|
+
def extract_regions_from_store(store)
|
|
132
|
+
region_list = store.variation_region_list
|
|
133
|
+
return [] unless region_list
|
|
134
|
+
|
|
135
|
+
regions = []
|
|
136
|
+
region_list.regions.each do |region_coords|
|
|
137
|
+
region = {}
|
|
138
|
+
region_coords.each_with_index do |axis_coords, axis_index|
|
|
139
|
+
next if axis_index >= @interpolator.axes.length
|
|
140
|
+
|
|
141
|
+
axis = @interpolator.axes[axis_index]
|
|
142
|
+
region[axis.axis_tag] = {
|
|
143
|
+
start: axis_coords.start,
|
|
144
|
+
peak: axis_coords.peak,
|
|
145
|
+
end: axis_coords.end_value,
|
|
146
|
+
}
|
|
147
|
+
end
|
|
148
|
+
regions << region
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
regions
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Apply horizontal metrics deltas
|
|
155
|
+
#
|
|
156
|
+
# @param hvar [Hvar] HVAR table
|
|
157
|
+
# @param hmtx [Hmtx] hmtx table
|
|
158
|
+
# @param glyph_count [Integer] Number of glyphs
|
|
159
|
+
# @param scalars [Array<Float>] Region scalars
|
|
160
|
+
# @return [Array<Hash>] Adjusted metrics
|
|
161
|
+
def apply_horizontal_metrics_deltas(hvar, hmtx, glyph_count, scalars)
|
|
162
|
+
adjusted_metrics = []
|
|
163
|
+
|
|
164
|
+
glyph_count.times do |glyph_id|
|
|
165
|
+
base_metric = hmtx.metric_for(glyph_id)
|
|
166
|
+
next unless base_metric
|
|
167
|
+
|
|
168
|
+
# Get advance width deltas
|
|
169
|
+
advance_deltas = hvar.advance_width_delta_set(glyph_id) || []
|
|
170
|
+
lsb_deltas = hvar.lsb_delta_set(glyph_id) || []
|
|
171
|
+
|
|
172
|
+
# Apply deltas using scalars
|
|
173
|
+
new_advance = @interpolator.interpolate_value(
|
|
174
|
+
base_metric[:advance_width],
|
|
175
|
+
advance_deltas,
|
|
176
|
+
scalars,
|
|
177
|
+
).round
|
|
178
|
+
|
|
179
|
+
new_lsb = @interpolator.interpolate_value(
|
|
180
|
+
base_metric[:lsb],
|
|
181
|
+
lsb_deltas,
|
|
182
|
+
scalars,
|
|
183
|
+
).round
|
|
184
|
+
|
|
185
|
+
adjusted_metrics << {
|
|
186
|
+
advance_width: new_advance,
|
|
187
|
+
lsb: new_lsb,
|
|
188
|
+
}
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
adjusted_metrics
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Apply font-wide metrics deltas
|
|
195
|
+
#
|
|
196
|
+
# @param mvar [Mvar] MVAR table
|
|
197
|
+
# @param scalars [Array<Float>] Region scalars
|
|
198
|
+
def apply_font_wide_metrics_deltas(mvar, scalars)
|
|
199
|
+
# Get all metric tags
|
|
200
|
+
mvar.metric_tags.each do |tag|
|
|
201
|
+
delta_set = mvar.metric_delta_set(tag)
|
|
202
|
+
next unless delta_set
|
|
203
|
+
|
|
204
|
+
# Get base value for this metric
|
|
205
|
+
base_value = get_base_metric_value(tag)
|
|
206
|
+
next unless base_value
|
|
207
|
+
|
|
208
|
+
# Apply deltas
|
|
209
|
+
new_value = @interpolator.interpolate_value(
|
|
210
|
+
base_value,
|
|
211
|
+
delta_set,
|
|
212
|
+
scalars,
|
|
213
|
+
).round
|
|
214
|
+
|
|
215
|
+
# Update metric in appropriate table
|
|
216
|
+
update_font_metric(tag, new_value)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Get base metric value by tag
|
|
221
|
+
#
|
|
222
|
+
# @param tag [String] Metric tag (e.g., "hasc", "hdsc")
|
|
223
|
+
# @return [Integer, nil] Base metric value
|
|
224
|
+
def get_base_metric_value(tag)
|
|
225
|
+
case tag
|
|
226
|
+
when "hasc"
|
|
227
|
+
variation_table("hhea")&.ascender
|
|
228
|
+
when "hdsc"
|
|
229
|
+
variation_table("hhea")&.descender
|
|
230
|
+
when "hlgp"
|
|
231
|
+
variation_table("hhea")&.line_gap
|
|
232
|
+
when "xhgt"
|
|
233
|
+
os2 = variation_table("OS/2")
|
|
234
|
+
os2&.s_x_height if os2.respond_to?(:s_x_height)
|
|
235
|
+
when "cpht"
|
|
236
|
+
os2 = variation_table("OS/2")
|
|
237
|
+
os2&.s_cap_height if os2.respond_to?(:s_cap_height)
|
|
238
|
+
# Add more metrics as needed
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Update font metric in appropriate table
|
|
243
|
+
#
|
|
244
|
+
# @param tag [String] Metric tag
|
|
245
|
+
# @param value [Integer] New metric value
|
|
246
|
+
def update_font_metric(tag, value)
|
|
247
|
+
# This is a placeholder - full implementation would:
|
|
248
|
+
# 1. Modify the appropriate table's binary data
|
|
249
|
+
# 2. Update the font's table data
|
|
250
|
+
# For now, we just log the update
|
|
251
|
+
# In production, this would rebuild the affected tables
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Rebuild hmtx table with adjusted metrics
|
|
255
|
+
#
|
|
256
|
+
# @param metrics [Array<Hash>] Adjusted metrics
|
|
257
|
+
def rebuild_hmtx_table(metrics)
|
|
258
|
+
# Build new hmtx binary data
|
|
259
|
+
data = build_hmtx_data(metrics)
|
|
260
|
+
|
|
261
|
+
# Update font's table data
|
|
262
|
+
@font.table_data["hmtx"] = data if data
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Build hmtx binary data from metrics
|
|
266
|
+
#
|
|
267
|
+
# @param metrics [Array<Hash>] Metrics to encode
|
|
268
|
+
# @return [String, nil] Binary data
|
|
269
|
+
def build_hmtx_data(metrics)
|
|
270
|
+
return nil if metrics.empty?
|
|
271
|
+
|
|
272
|
+
# Find last unique advance width
|
|
273
|
+
last_advance = metrics.last[:advance_width]
|
|
274
|
+
number_of_h_metrics = metrics.length
|
|
275
|
+
|
|
276
|
+
# Optimize: count from end while advance width is same
|
|
277
|
+
(metrics.length - 1).downto(1) do |i|
|
|
278
|
+
break if metrics[i][:advance_width] != last_advance
|
|
279
|
+
|
|
280
|
+
number_of_h_metrics = i
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Build binary data
|
|
284
|
+
data = String.new("", encoding: Encoding::BINARY)
|
|
285
|
+
|
|
286
|
+
# Write hMetrics array
|
|
287
|
+
number_of_h_metrics.times do |i|
|
|
288
|
+
metric = metrics[i]
|
|
289
|
+
data << [metric[:advance_width]].pack("n") # uint16
|
|
290
|
+
data << [metric[:lsb]].pack("n") # int16 (as uint16, will be interpreted as signed)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Write remaining LSBs
|
|
294
|
+
(number_of_h_metrics...metrics.length).each do |i|
|
|
295
|
+
data << [metrics[i][:lsb]].pack("n") # int16
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Update hhea's numberOfHMetrics
|
|
299
|
+
update_hhea_number_of_h_metrics(number_of_h_metrics)
|
|
300
|
+
|
|
301
|
+
data
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Update hhea table's numberOfHMetrics field
|
|
305
|
+
#
|
|
306
|
+
# @param count [Integer] New numberOfHMetrics value
|
|
307
|
+
def update_hhea_number_of_h_metrics(count)
|
|
308
|
+
return unless has_variation_table?("hhea")
|
|
309
|
+
|
|
310
|
+
hhea = variation_table("hhea")
|
|
311
|
+
return unless hhea
|
|
312
|
+
|
|
313
|
+
# Update the field if hhea supports it
|
|
314
|
+
hhea.number_of_h_metrics = count if hhea.respond_to?(:number_of_h_metrics=)
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|