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,234 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../binary/base_record"
|
|
4
|
+
require_relative "variation_common"
|
|
5
|
+
|
|
6
|
+
module Fontisan
|
|
7
|
+
module Tables
|
|
8
|
+
# Parser for the 'VVAR' (Vertical Metrics Variations) table
|
|
9
|
+
#
|
|
10
|
+
# The VVAR table provides variation data for vertical metrics including:
|
|
11
|
+
# - Advance height variations
|
|
12
|
+
# - Top side bearing (TSB) variations
|
|
13
|
+
#
|
|
14
|
+
# This table uses the ItemVariationStore structure to efficiently store
|
|
15
|
+
# delta values for different regions in the design space.
|
|
16
|
+
#
|
|
17
|
+
# Reference: OpenType specification, VVAR table
|
|
18
|
+
#
|
|
19
|
+
# @example Reading a VVAR table
|
|
20
|
+
# data = font.table_data("VVAR")
|
|
21
|
+
# vvar = Fontisan::Tables::Vvar.read(data)
|
|
22
|
+
# advance_deltas = vvar.advance_height_deltas(glyph_id, coordinates)
|
|
23
|
+
class Vvar < Binary::BaseRecord
|
|
24
|
+
uint16 :major_version
|
|
25
|
+
uint16 :minor_version
|
|
26
|
+
uint32 :item_variation_store_offset
|
|
27
|
+
uint32 :advance_height_mapping_offset
|
|
28
|
+
uint32 :tsb_mapping_offset
|
|
29
|
+
uint32 :bsb_mapping_offset
|
|
30
|
+
uint32 :v_org_mapping_offset
|
|
31
|
+
|
|
32
|
+
# Get version as a float
|
|
33
|
+
#
|
|
34
|
+
# @return [Float] Version number (e.g., 1.0)
|
|
35
|
+
def version
|
|
36
|
+
major_version + (minor_version / 10.0)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Parse the item variation store
|
|
40
|
+
#
|
|
41
|
+
# @return [VariationCommon::ItemVariationStore, nil] Variation store
|
|
42
|
+
def item_variation_store
|
|
43
|
+
return @item_variation_store if defined?(@item_variation_store)
|
|
44
|
+
return @item_variation_store = nil if item_variation_store_offset.zero?
|
|
45
|
+
|
|
46
|
+
data = raw_data
|
|
47
|
+
offset = item_variation_store_offset
|
|
48
|
+
|
|
49
|
+
return @item_variation_store = nil if offset >= data.bytesize
|
|
50
|
+
|
|
51
|
+
store_data = data.byteslice(offset..-1)
|
|
52
|
+
@item_variation_store = VariationCommon::ItemVariationStore.read(store_data)
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
warn "Failed to parse VVAR item variation store: #{e.message}"
|
|
55
|
+
@item_variation_store = nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Parse advance height mapping
|
|
59
|
+
#
|
|
60
|
+
# @return [VariationCommon::DeltaSetIndexMap, nil] Advance height map
|
|
61
|
+
def advance_height_mapping
|
|
62
|
+
return @advance_height_mapping if defined?(@advance_height_mapping)
|
|
63
|
+
return @advance_height_mapping = nil if advance_height_mapping_offset.zero?
|
|
64
|
+
|
|
65
|
+
data = raw_data
|
|
66
|
+
offset = advance_height_mapping_offset
|
|
67
|
+
|
|
68
|
+
return @advance_height_mapping = nil if offset >= data.bytesize
|
|
69
|
+
|
|
70
|
+
map_data = data.byteslice(offset..-1)
|
|
71
|
+
@advance_height_mapping = VariationCommon::DeltaSetIndexMap.read(map_data)
|
|
72
|
+
rescue StandardError => e
|
|
73
|
+
warn "Failed to parse VVAR advance height mapping: #{e.message}"
|
|
74
|
+
@advance_height_mapping = nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Parse TSB (top side bearing) mapping
|
|
78
|
+
#
|
|
79
|
+
# @return [VariationCommon::DeltaSetIndexMap, nil] TSB map
|
|
80
|
+
def tsb_mapping
|
|
81
|
+
return @tsb_mapping if defined?(@tsb_mapping)
|
|
82
|
+
return @tsb_mapping = nil if tsb_mapping_offset.zero?
|
|
83
|
+
|
|
84
|
+
data = raw_data
|
|
85
|
+
offset = tsb_mapping_offset
|
|
86
|
+
|
|
87
|
+
return @tsb_mapping = nil if offset >= data.bytesize
|
|
88
|
+
|
|
89
|
+
map_data = data.byteslice(offset..-1)
|
|
90
|
+
@tsb_mapping = VariationCommon::DeltaSetIndexMap.read(map_data)
|
|
91
|
+
rescue StandardError => e
|
|
92
|
+
warn "Failed to parse VVAR TSB mapping: #{e.message}"
|
|
93
|
+
@tsb_mapping = nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Parse BSB (bottom side bearing) mapping
|
|
97
|
+
#
|
|
98
|
+
# @return [VariationCommon::DeltaSetIndexMap, nil] BSB map
|
|
99
|
+
def bsb_mapping
|
|
100
|
+
return @bsb_mapping if defined?(@bsb_mapping)
|
|
101
|
+
return @bsb_mapping = nil if bsb_mapping_offset.zero?
|
|
102
|
+
|
|
103
|
+
data = raw_data
|
|
104
|
+
offset = bsb_mapping_offset
|
|
105
|
+
|
|
106
|
+
return @bsb_mapping = nil if offset >= data.bytesize
|
|
107
|
+
|
|
108
|
+
map_data = data.byteslice(offset..-1)
|
|
109
|
+
@bsb_mapping = VariationCommon::DeltaSetIndexMap.read(map_data)
|
|
110
|
+
rescue StandardError => e
|
|
111
|
+
warn "Failed to parse VVAR BSB mapping: #{e.message}"
|
|
112
|
+
@bsb_mapping = nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Parse vertical origin mapping
|
|
116
|
+
#
|
|
117
|
+
# @return [VariationCommon::DeltaSetIndexMap, nil] VOrig map
|
|
118
|
+
def v_org_mapping
|
|
119
|
+
return @v_org_mapping if defined?(@v_org_mapping)
|
|
120
|
+
return @v_org_mapping = nil if v_org_mapping_offset.zero?
|
|
121
|
+
|
|
122
|
+
data = raw_data
|
|
123
|
+
offset = v_org_mapping_offset
|
|
124
|
+
|
|
125
|
+
return @v_org_mapping = nil if offset >= data.bytesize
|
|
126
|
+
|
|
127
|
+
map_data = data.byteslice(offset..-1)
|
|
128
|
+
@v_org_mapping = VariationCommon::DeltaSetIndexMap.read(map_data)
|
|
129
|
+
rescue StandardError => e
|
|
130
|
+
warn "Failed to parse VVAR vertical origin mapping: #{e.message}"
|
|
131
|
+
@v_org_mapping = nil
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Get advance height delta set for a glyph
|
|
135
|
+
#
|
|
136
|
+
# @param glyph_id [Integer] Glyph ID
|
|
137
|
+
# @return [Array<Integer>, nil] Delta values or nil
|
|
138
|
+
def advance_height_delta_set(glyph_id)
|
|
139
|
+
return nil unless item_variation_store
|
|
140
|
+
|
|
141
|
+
# If no mapping, use glyph_id directly
|
|
142
|
+
if advance_height_mapping.nil?
|
|
143
|
+
return item_variation_store.delta_set(0, glyph_id)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Use mapping to get delta set indices
|
|
147
|
+
map_data = advance_height_mapping.map_data
|
|
148
|
+
return nil if glyph_id >= map_data.length
|
|
149
|
+
|
|
150
|
+
delta_index = map_data[glyph_id]
|
|
151
|
+
outer_index = (delta_index >> 16) & 0xFFFF
|
|
152
|
+
inner_index = delta_index & 0xFFFF
|
|
153
|
+
|
|
154
|
+
item_variation_store.delta_set(outer_index, inner_index)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Get TSB delta set for a glyph
|
|
158
|
+
#
|
|
159
|
+
# @param glyph_id [Integer] Glyph ID
|
|
160
|
+
# @return [Array<Integer>, nil] Delta values or nil
|
|
161
|
+
def tsb_delta_set(glyph_id)
|
|
162
|
+
return nil unless item_variation_store
|
|
163
|
+
|
|
164
|
+
# If no mapping, use glyph_id directly
|
|
165
|
+
if tsb_mapping.nil?
|
|
166
|
+
return item_variation_store.delta_set(0, glyph_id)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Use mapping to get delta set indices
|
|
170
|
+
map_data = tsb_mapping.map_data
|
|
171
|
+
return nil if glyph_id >= map_data.length
|
|
172
|
+
|
|
173
|
+
delta_index = map_data[glyph_id]
|
|
174
|
+
outer_index = (delta_index >> 16) & 0xFFFF
|
|
175
|
+
inner_index = delta_index & 0xFFFF
|
|
176
|
+
|
|
177
|
+
item_variation_store.delta_set(outer_index, inner_index)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Get BSB delta set for a glyph
|
|
181
|
+
#
|
|
182
|
+
# @param glyph_id [Integer] Glyph ID
|
|
183
|
+
# @return [Array<Integer>, nil] Delta values or nil
|
|
184
|
+
def bsb_delta_set(glyph_id)
|
|
185
|
+
return nil unless item_variation_store
|
|
186
|
+
|
|
187
|
+
# If no mapping, use glyph_id directly
|
|
188
|
+
if bsb_mapping.nil?
|
|
189
|
+
return item_variation_store.delta_set(0, glyph_id)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Use mapping to get delta set indices
|
|
193
|
+
map_data = bsb_mapping.map_data
|
|
194
|
+
return nil if glyph_id >= map_data.length
|
|
195
|
+
|
|
196
|
+
delta_index = map_data[glyph_id]
|
|
197
|
+
outer_index = (delta_index >> 16) & 0xFFFF
|
|
198
|
+
inner_index = delta_index & 0xFFFF
|
|
199
|
+
|
|
200
|
+
item_variation_store.delta_set(outer_index, inner_index)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Get vertical origin delta set for a glyph
|
|
204
|
+
#
|
|
205
|
+
# @param glyph_id [Integer] Glyph ID
|
|
206
|
+
# @return [Array<Integer>, nil] Delta values or nil
|
|
207
|
+
def v_org_delta_set(glyph_id)
|
|
208
|
+
return nil unless item_variation_store
|
|
209
|
+
|
|
210
|
+
# If no mapping, use glyph_id directly
|
|
211
|
+
if v_org_mapping.nil?
|
|
212
|
+
return item_variation_store.delta_set(0, glyph_id)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Use mapping to get delta set indices
|
|
216
|
+
map_data = v_org_mapping.map_data
|
|
217
|
+
return nil if glyph_id >= map_data.length
|
|
218
|
+
|
|
219
|
+
delta_index = map_data[glyph_id]
|
|
220
|
+
outer_index = (delta_index >> 16) & 0xFFFF
|
|
221
|
+
inner_index = delta_index & 0xFFFF
|
|
222
|
+
|
|
223
|
+
item_variation_store.delta_set(outer_index, inner_index)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Check if table is valid
|
|
227
|
+
#
|
|
228
|
+
# @return [Boolean] True if valid
|
|
229
|
+
def valid?
|
|
230
|
+
major_version == 1 && minor_version.zero?
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
@@ -64,12 +64,13 @@ module Fontisan
|
|
|
64
64
|
#
|
|
65
65
|
# @param index [Integer] Index of the font (0-based)
|
|
66
66
|
# @param io [IO] Open file handle
|
|
67
|
+
# @param mode [Symbol] Loading mode (:metadata or :full, default: :full)
|
|
67
68
|
# @return [TrueTypeFont, nil] Font object or nil if index out of range
|
|
68
|
-
def font(index, io)
|
|
69
|
+
def font(index, io, mode: LoadingModes::FULL)
|
|
69
70
|
return nil if index >= num_fonts
|
|
70
71
|
|
|
71
72
|
require_relative "true_type_font"
|
|
72
|
-
TrueTypeFont.from_ttc(io, font_offsets[index])
|
|
73
|
+
TrueTypeFont.from_ttc(io, font_offsets[index], mode: mode)
|
|
73
74
|
end
|
|
74
75
|
|
|
75
76
|
# Get font count (Fontisan extension)
|
|
@@ -94,5 +95,158 @@ module Fontisan
|
|
|
94
95
|
def version
|
|
95
96
|
(major_version << 16) | minor_version
|
|
96
97
|
end
|
|
98
|
+
|
|
99
|
+
# List all fonts in the collection with basic metadata
|
|
100
|
+
#
|
|
101
|
+
# Returns a CollectionListInfo model containing summaries of all fonts.
|
|
102
|
+
# This is the API method used by the `ls` command for collections.
|
|
103
|
+
#
|
|
104
|
+
# @param io [IO] Open file handle to read fonts from
|
|
105
|
+
# @return [CollectionListInfo] List of fonts with metadata
|
|
106
|
+
#
|
|
107
|
+
# @example List fonts in collection
|
|
108
|
+
# File.open("fonts.ttc", "rb") do |io|
|
|
109
|
+
# ttc = TrueTypeCollection.read(io)
|
|
110
|
+
# list = ttc.list_fonts(io)
|
|
111
|
+
# list.fonts.each { |f| puts "#{f.index}: #{f.family_name}" }
|
|
112
|
+
# end
|
|
113
|
+
def list_fonts(io)
|
|
114
|
+
require_relative "models/collection_list_info"
|
|
115
|
+
require_relative "models/collection_font_summary"
|
|
116
|
+
require_relative "true_type_font"
|
|
117
|
+
require_relative "tables/name"
|
|
118
|
+
|
|
119
|
+
fonts = font_offsets.map.with_index do |offset, index|
|
|
120
|
+
font = TrueTypeFont.from_ttc(io, offset)
|
|
121
|
+
|
|
122
|
+
# Extract basic font info
|
|
123
|
+
name_table = font.table("name")
|
|
124
|
+
post_table = font.table("post")
|
|
125
|
+
|
|
126
|
+
family_name = name_table&.english_name(Tables::Name::FAMILY) || "Unknown"
|
|
127
|
+
subfamily_name = name_table&.english_name(Tables::Name::SUBFAMILY) || "Regular"
|
|
128
|
+
postscript_name = name_table&.english_name(Tables::Name::POSTSCRIPT_NAME) || "Unknown"
|
|
129
|
+
|
|
130
|
+
# Determine font format
|
|
131
|
+
sfnt = font.header.sfnt_version
|
|
132
|
+
font_format = case sfnt
|
|
133
|
+
when 0x00010000, 0x74727565 # 0x74727565 = 'true'
|
|
134
|
+
"TrueType"
|
|
135
|
+
when 0x4F54544F # 'OTTO'
|
|
136
|
+
"OpenType"
|
|
137
|
+
else
|
|
138
|
+
"Unknown"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
num_glyphs = post_table&.glyph_names&.length || 0
|
|
142
|
+
num_tables = font.table_names.length
|
|
143
|
+
|
|
144
|
+
Models::CollectionFontSummary.new(
|
|
145
|
+
index: index,
|
|
146
|
+
family_name: family_name,
|
|
147
|
+
subfamily_name: subfamily_name,
|
|
148
|
+
postscript_name: postscript_name,
|
|
149
|
+
font_format: font_format,
|
|
150
|
+
num_glyphs: num_glyphs,
|
|
151
|
+
num_tables: num_tables,
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
Models::CollectionListInfo.new(
|
|
156
|
+
collection_path: nil, # Will be set by command
|
|
157
|
+
num_fonts: num_fonts,
|
|
158
|
+
fonts: fonts,
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Get comprehensive collection metadata
|
|
163
|
+
#
|
|
164
|
+
# Returns a CollectionInfo model with header information, offsets,
|
|
165
|
+
# and table sharing statistics.
|
|
166
|
+
# This is the API method used by the `info` command for collections.
|
|
167
|
+
#
|
|
168
|
+
# @param io [IO] Open file handle to read fonts from
|
|
169
|
+
# @param path [String] Collection file path (for file size)
|
|
170
|
+
# @return [CollectionInfo] Collection metadata
|
|
171
|
+
#
|
|
172
|
+
# @example Get collection info
|
|
173
|
+
# File.open("fonts.ttc", "rb") do |io|
|
|
174
|
+
# ttc = TrueTypeCollection.read(io)
|
|
175
|
+
# info = ttc.collection_info(io, "fonts.ttc")
|
|
176
|
+
# puts "Version: #{info.version_string}"
|
|
177
|
+
# end
|
|
178
|
+
def collection_info(io, path)
|
|
179
|
+
require_relative "models/collection_info"
|
|
180
|
+
require_relative "models/table_sharing_info"
|
|
181
|
+
|
|
182
|
+
# Calculate table sharing statistics
|
|
183
|
+
table_sharing = calculate_table_sharing(io)
|
|
184
|
+
|
|
185
|
+
# Get file size
|
|
186
|
+
file_size = path ? File.size(path) : 0
|
|
187
|
+
|
|
188
|
+
Models::CollectionInfo.new(
|
|
189
|
+
collection_path: path,
|
|
190
|
+
collection_format: "TTC",
|
|
191
|
+
ttc_tag: tag,
|
|
192
|
+
major_version: major_version,
|
|
193
|
+
minor_version: minor_version,
|
|
194
|
+
num_fonts: num_fonts,
|
|
195
|
+
font_offsets: font_offsets.to_a,
|
|
196
|
+
file_size_bytes: file_size,
|
|
197
|
+
table_sharing: table_sharing,
|
|
198
|
+
)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
# Calculate table sharing statistics
|
|
204
|
+
#
|
|
205
|
+
# Analyzes which tables are shared between fonts and calculates
|
|
206
|
+
# space savings from deduplication.
|
|
207
|
+
#
|
|
208
|
+
# @param io [IO] Open file handle
|
|
209
|
+
# @return [TableSharingInfo] Sharing statistics
|
|
210
|
+
def calculate_table_sharing(io)
|
|
211
|
+
require_relative "models/table_sharing_info"
|
|
212
|
+
require_relative "true_type_font"
|
|
213
|
+
|
|
214
|
+
# Extract all fonts
|
|
215
|
+
fonts = font_offsets.map do |offset|
|
|
216
|
+
TrueTypeFont.from_ttc(io, offset)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Build table hash map (checksum -> size)
|
|
220
|
+
table_map = {}
|
|
221
|
+
total_table_size = 0
|
|
222
|
+
|
|
223
|
+
fonts.each do |font|
|
|
224
|
+
font.tables.each do |entry|
|
|
225
|
+
key = entry.checksum
|
|
226
|
+
size = entry.table_length
|
|
227
|
+
table_map[key] ||= size
|
|
228
|
+
total_table_size += size
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Count unique vs shared
|
|
233
|
+
unique_tables = table_map.size
|
|
234
|
+
total_tables = fonts.sum { |f| f.tables.length }
|
|
235
|
+
shared_tables = total_tables - unique_tables
|
|
236
|
+
|
|
237
|
+
# Calculate space saved
|
|
238
|
+
unique_size = table_map.values.sum
|
|
239
|
+
space_saved = total_table_size - unique_size
|
|
240
|
+
|
|
241
|
+
# Calculate sharing percentage
|
|
242
|
+
sharing_pct = total_tables.positive? ? (shared_tables.to_f / total_tables * 100).round(2) : 0.0
|
|
243
|
+
|
|
244
|
+
Models::TableSharingInfo.new(
|
|
245
|
+
shared_tables: shared_tables,
|
|
246
|
+
unique_tables: unique_tables,
|
|
247
|
+
sharing_percentage: sharing_pct,
|
|
248
|
+
space_saved_bytes: space_saved,
|
|
249
|
+
)
|
|
250
|
+
end
|
|
97
251
|
end
|
|
98
252
|
end
|