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,461 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Subset
|
|
5
|
+
# Table-specific subsetting strategies
|
|
6
|
+
#
|
|
7
|
+
# This class provides methods for subsetting individual font tables according
|
|
8
|
+
# to the glyph mapping. Each table type has different subsetting requirements:
|
|
9
|
+
#
|
|
10
|
+
# - maxp: Update glyph count
|
|
11
|
+
# - hhea: Update horizontal metrics count
|
|
12
|
+
# - hmtx: Subset horizontal metrics
|
|
13
|
+
# - glyf: Subset glyph data and remap component references
|
|
14
|
+
# - loca: Rebuild glyph location index
|
|
15
|
+
# - cmap: Remap character to glyph mappings
|
|
16
|
+
# - post: Optionally drop glyph names
|
|
17
|
+
# - name: Pass through (no subsetting needed)
|
|
18
|
+
# - head: Update checksum adjustment (handled by FontWriter)
|
|
19
|
+
# - OS/2: Optionally prune Unicode ranges
|
|
20
|
+
#
|
|
21
|
+
# The subsetting process preserves font validity by updating all references
|
|
22
|
+
# and recalculating offsets and checksums.
|
|
23
|
+
#
|
|
24
|
+
# @example Subset a single table
|
|
25
|
+
# subsetter = TableSubsetter.new(font, mapping, options)
|
|
26
|
+
# maxp_data = subsetter.subset_maxp(maxp_table)
|
|
27
|
+
#
|
|
28
|
+
# @example Subset all tables
|
|
29
|
+
# subsetter = TableSubsetter.new(font, mapping, options)
|
|
30
|
+
# subset_tables = {}
|
|
31
|
+
# profile_tables.each do |tag|
|
|
32
|
+
# table = font.table(tag)
|
|
33
|
+
# subset_tables[tag] = subsetter.subset_table(tag, table) if table
|
|
34
|
+
# end
|
|
35
|
+
class TableSubsetter
|
|
36
|
+
# Font instance being subset
|
|
37
|
+
# @return [TrueTypeFont, OpenTypeFont]
|
|
38
|
+
attr_reader :font
|
|
39
|
+
|
|
40
|
+
# Glyph ID mapping (old GID → new GID)
|
|
41
|
+
# @return [GlyphMapping]
|
|
42
|
+
attr_reader :mapping
|
|
43
|
+
|
|
44
|
+
# Subsetting options
|
|
45
|
+
# @return [Options]
|
|
46
|
+
attr_reader :options
|
|
47
|
+
|
|
48
|
+
# Initialize table subsetter
|
|
49
|
+
#
|
|
50
|
+
# @param font [TrueTypeFont, OpenTypeFont] Font to subset
|
|
51
|
+
# @param mapping [GlyphMapping] Glyph ID mapping
|
|
52
|
+
# @param options [Options] Subsetting options
|
|
53
|
+
def initialize(font, mapping, options)
|
|
54
|
+
@font = font
|
|
55
|
+
@mapping = mapping
|
|
56
|
+
@options = options
|
|
57
|
+
@glyf_data = nil
|
|
58
|
+
@loca_offsets = nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Subset a table by tag
|
|
62
|
+
#
|
|
63
|
+
# Delegates to table-specific subsetting methods. Unknown tables
|
|
64
|
+
# are passed through unchanged.
|
|
65
|
+
#
|
|
66
|
+
# @param tag [String] Table tag (e.g., "glyf", "hmtx")
|
|
67
|
+
# @param table [Object] Parsed table object
|
|
68
|
+
# @return [String] Binary data of subset table
|
|
69
|
+
def subset_table(tag, table)
|
|
70
|
+
case tag
|
|
71
|
+
when "maxp"
|
|
72
|
+
subset_maxp(table)
|
|
73
|
+
when "hhea"
|
|
74
|
+
subset_hhea(table)
|
|
75
|
+
when "hmtx"
|
|
76
|
+
subset_hmtx(table)
|
|
77
|
+
when "loca"
|
|
78
|
+
subset_loca(table)
|
|
79
|
+
when "glyf"
|
|
80
|
+
subset_glyf(table)
|
|
81
|
+
when "cmap"
|
|
82
|
+
subset_cmap(table)
|
|
83
|
+
when "post"
|
|
84
|
+
subset_post(table)
|
|
85
|
+
when "name"
|
|
86
|
+
subset_name(table)
|
|
87
|
+
when "head"
|
|
88
|
+
subset_head(table)
|
|
89
|
+
when "OS/2"
|
|
90
|
+
subset_os2(table)
|
|
91
|
+
else
|
|
92
|
+
# Unknown tables pass through unchanged
|
|
93
|
+
font.table_data[tag]
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Subset maxp table (update numGlyphs)
|
|
98
|
+
#
|
|
99
|
+
# Updates the numGlyphs field to reflect the number of glyphs in
|
|
100
|
+
# the subset font.
|
|
101
|
+
#
|
|
102
|
+
# @param table [Maxp] Parsed maxp table
|
|
103
|
+
# @return [String] Binary data of subset maxp table
|
|
104
|
+
def subset_maxp(table)
|
|
105
|
+
data = table.to_binary_s.dup
|
|
106
|
+
|
|
107
|
+
# Update numGlyphs field (at offset 4, uint16)
|
|
108
|
+
data[4, 2] = [mapping.size].pack("n")
|
|
109
|
+
|
|
110
|
+
data
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Subset hhea table (update numberOfHMetrics)
|
|
114
|
+
#
|
|
115
|
+
# Updates the numberOfHMetrics field to reflect the number of
|
|
116
|
+
# horizontal metrics in the subset font.
|
|
117
|
+
#
|
|
118
|
+
# @param table [Hhea] Parsed hhea table
|
|
119
|
+
# @param hmtx [Hmtx, nil] Optional parsed hmtx table (for calculating metrics)
|
|
120
|
+
# @return [String] Binary data of subset hhea table
|
|
121
|
+
def subset_hhea(table, hmtx = nil)
|
|
122
|
+
data = table.to_binary_s.dup
|
|
123
|
+
|
|
124
|
+
# Calculate new numberOfHMetrics
|
|
125
|
+
new_num_h_metrics = if hmtx && hmtx.h_metrics
|
|
126
|
+
hmtx.h_metrics.size
|
|
127
|
+
else
|
|
128
|
+
calculate_number_of_h_metrics
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Update numberOfHMetrics field (at offset 34, uint16)
|
|
132
|
+
data[34, 2] = [new_num_h_metrics].pack("n")
|
|
133
|
+
|
|
134
|
+
data
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Subset hmtx table (subset horizontal metrics)
|
|
138
|
+
#
|
|
139
|
+
# Builds new hmtx table with metrics for subset glyphs only,
|
|
140
|
+
# preserving the order of the glyph mapping.
|
|
141
|
+
#
|
|
142
|
+
# @param table [Hmtx] Parsed hmtx table
|
|
143
|
+
# @return [String] Binary data of subset hmtx table
|
|
144
|
+
def subset_hmtx(table)
|
|
145
|
+
# Ensure hmtx is parsed
|
|
146
|
+
unless table.parsed?
|
|
147
|
+
hhea = font.table("hhea")
|
|
148
|
+
maxp = font.table("maxp")
|
|
149
|
+
table.parse_with_context(hhea.number_of_h_metrics, maxp.num_glyphs)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Build new hmtx data
|
|
153
|
+
data = String.new(encoding: Encoding::BINARY)
|
|
154
|
+
|
|
155
|
+
mapping.each do |old_id, _new_id|
|
|
156
|
+
metric = table.metric_for(old_id)
|
|
157
|
+
next unless metric
|
|
158
|
+
|
|
159
|
+
data << [metric[:advance_width]].pack("n")
|
|
160
|
+
data << [metric[:lsb]].pack("n")
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
data
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Subset glyf table (subset glyph data)
|
|
167
|
+
#
|
|
168
|
+
# Extracts glyph data for subset glyphs and remaps component
|
|
169
|
+
# references in compound glyphs. Also builds loca offsets.
|
|
170
|
+
#
|
|
171
|
+
# @param table [Glyf] Parsed glyf table
|
|
172
|
+
# @return [String] Binary data of subset glyf table
|
|
173
|
+
def subset_glyf(table)
|
|
174
|
+
# Build glyf and loca together
|
|
175
|
+
build_glyf_and_loca(table)
|
|
176
|
+
@glyf_data
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Subset loca table (rebuild glyph location index)
|
|
180
|
+
#
|
|
181
|
+
# Builds new loca table based on subset glyph offsets. Must be
|
|
182
|
+
# called after subset_glyf.
|
|
183
|
+
#
|
|
184
|
+
# @param table [Loca] Parsed loca table
|
|
185
|
+
# @return [String] Binary data of subset loca table
|
|
186
|
+
def subset_loca(_table)
|
|
187
|
+
# Build glyf and loca together if not already done
|
|
188
|
+
glyf = font.table("glyf")
|
|
189
|
+
build_glyf_and_loca(glyf) unless @loca_offsets
|
|
190
|
+
|
|
191
|
+
head = font.table("head")
|
|
192
|
+
format = head.index_to_loc_format
|
|
193
|
+
|
|
194
|
+
data = String.new(encoding: Encoding::BINARY)
|
|
195
|
+
|
|
196
|
+
if format.zero?
|
|
197
|
+
# Short format: offsets / 2 as uint16
|
|
198
|
+
@loca_offsets.each do |offset|
|
|
199
|
+
data << [offset / 2].pack("n")
|
|
200
|
+
end
|
|
201
|
+
else
|
|
202
|
+
# Long format: offsets as uint32
|
|
203
|
+
@loca_offsets.each do |offset|
|
|
204
|
+
data << [offset].pack("N")
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
data
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Subset cmap table (remap character to glyph mappings)
|
|
212
|
+
#
|
|
213
|
+
# Builds new cmap table with only mappings for glyphs in the subset.
|
|
214
|
+
# Updates glyph IDs to new values from the mapping.
|
|
215
|
+
#
|
|
216
|
+
# @param table [Cmap] Parsed cmap table
|
|
217
|
+
# @return [String] Binary data of subset cmap table
|
|
218
|
+
def subset_cmap(table)
|
|
219
|
+
# Get old mappings
|
|
220
|
+
old_mappings = table.unicode_mappings
|
|
221
|
+
new_mappings = {}
|
|
222
|
+
|
|
223
|
+
# Remap to new glyph IDs
|
|
224
|
+
old_mappings.each do |char_code, old_gid|
|
|
225
|
+
new_gid = mapping.new_id(old_gid)
|
|
226
|
+
new_mappings[char_code] = new_gid if new_gid
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Build cmap binary with new mappings
|
|
230
|
+
build_cmap_binary(new_mappings)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Subset post table (optionally drop glyph names)
|
|
234
|
+
#
|
|
235
|
+
# If drop_names option is set, converts to post version 3.0
|
|
236
|
+
# (no glyph names). Otherwise passes through unchanged.
|
|
237
|
+
#
|
|
238
|
+
# @param table [Post] Parsed post table
|
|
239
|
+
# @return [String] Binary data of subset post table
|
|
240
|
+
def subset_post(table)
|
|
241
|
+
if options.drop_names
|
|
242
|
+
# Build post table version 3.0 (no glyph names)
|
|
243
|
+
build_post_v3(table)
|
|
244
|
+
else
|
|
245
|
+
# Keep as-is
|
|
246
|
+
font.table_data["post"]
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Subset name table (pass through)
|
|
251
|
+
#
|
|
252
|
+
# Name table doesn't require subsetting, pass through unchanged.
|
|
253
|
+
#
|
|
254
|
+
# @param table [Name] Parsed name table
|
|
255
|
+
# @return [String] Binary data of subset name table
|
|
256
|
+
def subset_name(_table)
|
|
257
|
+
font.table_data["name"]
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Subset head table (pass through)
|
|
261
|
+
#
|
|
262
|
+
# head table will have checksum updated by FontWriter,
|
|
263
|
+
# no subsetting needed.
|
|
264
|
+
#
|
|
265
|
+
# @param table [Head] Parsed head table
|
|
266
|
+
# @return [String] Binary data of subset head table
|
|
267
|
+
def subset_head(_table)
|
|
268
|
+
font.table_data["head"]
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Subset OS/2 table (optionally prune Unicode ranges)
|
|
272
|
+
#
|
|
273
|
+
# If unicode_ranges option is set, updates Unicode range bits
|
|
274
|
+
# to reflect only the characters in the subset.
|
|
275
|
+
#
|
|
276
|
+
# @param table [Os2] Parsed OS/2 table
|
|
277
|
+
# @return [String] Binary data of subset OS/2 table
|
|
278
|
+
def subset_os2(_table)
|
|
279
|
+
if options.unicode_ranges
|
|
280
|
+
# TODO: Implement Unicode range pruning
|
|
281
|
+
# For now, pass through
|
|
282
|
+
end
|
|
283
|
+
font.table_data["OS/2"]
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
private
|
|
287
|
+
|
|
288
|
+
# Calculate numberOfHMetrics for subset
|
|
289
|
+
#
|
|
290
|
+
# For now, use the size of the mapping. In the future, this could
|
|
291
|
+
# be optimized by finding the last unique advance width.
|
|
292
|
+
#
|
|
293
|
+
# @return [Integer] Number of unique advance widths
|
|
294
|
+
def calculate_number_of_h_metrics
|
|
295
|
+
mapping.size
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Build glyf and loca tables together
|
|
299
|
+
#
|
|
300
|
+
# This method extracts glyph data for all glyphs in the mapping,
|
|
301
|
+
# remaps component references in compound glyphs, and builds the
|
|
302
|
+
# loca offset array.
|
|
303
|
+
#
|
|
304
|
+
# @param glyf_table [Glyf] Parsed glyf table
|
|
305
|
+
def build_glyf_and_loca(glyf_table)
|
|
306
|
+
return if @glyf_data && @loca_offsets
|
|
307
|
+
|
|
308
|
+
loca = font.table("loca")
|
|
309
|
+
head = font.table("head")
|
|
310
|
+
|
|
311
|
+
# Ensure loca is parsed
|
|
312
|
+
unless loca.parsed?
|
|
313
|
+
maxp = font.table("maxp")
|
|
314
|
+
loca.parse_with_context(head.index_to_loc_format, maxp.num_glyphs)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
@glyf_data = String.new(encoding: Encoding::BINARY)
|
|
318
|
+
@loca_offsets = []
|
|
319
|
+
current_offset = 0
|
|
320
|
+
|
|
321
|
+
# Process glyphs in mapping order
|
|
322
|
+
mapping.each do |old_id, _new_id|
|
|
323
|
+
@loca_offsets << current_offset
|
|
324
|
+
|
|
325
|
+
# Get offset and size from original loca
|
|
326
|
+
offset = loca.offset_for(old_id)
|
|
327
|
+
size = loca.size_of(old_id)
|
|
328
|
+
|
|
329
|
+
# Empty glyph
|
|
330
|
+
if size.nil? || size.zero?
|
|
331
|
+
next
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Extract glyph data
|
|
335
|
+
glyph_data = glyf_table.raw_data[offset, size]
|
|
336
|
+
|
|
337
|
+
# Check if compound glyph and remap components
|
|
338
|
+
if compound_glyph?(glyph_data)
|
|
339
|
+
glyph_data = remap_compound_glyph(glyph_data)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Add to new glyf data
|
|
343
|
+
@glyf_data << glyph_data
|
|
344
|
+
current_offset += glyph_data.bytesize
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Add final offset
|
|
348
|
+
@loca_offsets << current_offset
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Check if glyph data represents a compound glyph
|
|
352
|
+
#
|
|
353
|
+
# @param data [String] Glyph binary data
|
|
354
|
+
# @return [Boolean] True if compound glyph
|
|
355
|
+
def compound_glyph?(data)
|
|
356
|
+
return false if data.length < 2
|
|
357
|
+
|
|
358
|
+
num_contours_raw = data[0, 2].unpack1("n")
|
|
359
|
+
num_contours = to_signed_16(num_contours_raw)
|
|
360
|
+
num_contours == -1
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Remap component glyph IDs in compound glyph
|
|
364
|
+
#
|
|
365
|
+
# @param data [String] Original compound glyph data
|
|
366
|
+
# @return [String] Remapped compound glyph data
|
|
367
|
+
def remap_compound_glyph(data)
|
|
368
|
+
# Create a mutable copy
|
|
369
|
+
new_data = data.dup
|
|
370
|
+
offset = 10 # Skip header (10 bytes)
|
|
371
|
+
|
|
372
|
+
loop do
|
|
373
|
+
break if offset >= new_data.length - 4
|
|
374
|
+
|
|
375
|
+
# Read flags and old glyph index
|
|
376
|
+
flags = new_data[offset, 2].unpack1("n")
|
|
377
|
+
old_glyph_index = new_data[offset + 2, 2].unpack1("n")
|
|
378
|
+
|
|
379
|
+
# Remap glyph index
|
|
380
|
+
new_glyph_index = mapping.new_id(old_glyph_index)
|
|
381
|
+
unless new_glyph_index
|
|
382
|
+
raise Fontisan::SubsettingError,
|
|
383
|
+
"Component glyph #{old_glyph_index} not in subset"
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Write new glyph index
|
|
387
|
+
new_data[offset + 2, 2] = [new_glyph_index].pack("n")
|
|
388
|
+
|
|
389
|
+
# Move to next component
|
|
390
|
+
offset += 4 # flags + glyph_index
|
|
391
|
+
|
|
392
|
+
# Skip arguments
|
|
393
|
+
offset += if (flags & 0x0001).zero?
|
|
394
|
+
2 # Two 8-bit arguments
|
|
395
|
+
else
|
|
396
|
+
4 # Two 16-bit arguments
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Skip transformation
|
|
400
|
+
if (flags & 0x0080) != 0
|
|
401
|
+
offset += 8 # 2x2 matrix
|
|
402
|
+
elsif (flags & 0x0040) != 0
|
|
403
|
+
offset += 4 # X and Y scale
|
|
404
|
+
elsif (flags & 0x0008) != 0
|
|
405
|
+
offset += 2 # Uniform scale
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Check if more components
|
|
409
|
+
break unless (flags & 0x0020) != 0
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
new_data
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# Build cmap binary from mappings
|
|
416
|
+
#
|
|
417
|
+
# Creates a minimal cmap table with format 4 subtable for BMP
|
|
418
|
+
# and format 12 for supplementary planes if needed.
|
|
419
|
+
#
|
|
420
|
+
# @param mappings [Hash<Integer, Integer>] Char code => glyph ID
|
|
421
|
+
# @return [String] Binary cmap data
|
|
422
|
+
def build_cmap_binary(_mappings)
|
|
423
|
+
# For now, pass through original cmap
|
|
424
|
+
# TODO: Implement proper cmap building
|
|
425
|
+
font.table_data["cmap"]
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# Build post table version 3.0 (no glyph names)
|
|
429
|
+
#
|
|
430
|
+
# @param table [Post] Original post table
|
|
431
|
+
# @return [String] Binary post v3.0 data
|
|
432
|
+
def build_post_v3(_table)
|
|
433
|
+
# Post v3.0 header (32 bytes) - same as v2.0 but version = 3.0
|
|
434
|
+
data = String.new(encoding: Encoding::BINARY)
|
|
435
|
+
|
|
436
|
+
# Version 3.0
|
|
437
|
+
data << [0x00030000].pack("N")
|
|
438
|
+
|
|
439
|
+
# Copy italic angle, underline position/thickness from original
|
|
440
|
+
original_data = font.table_data["post"]
|
|
441
|
+
data << if original_data.length >= 32
|
|
442
|
+
# Copy fields from offset 4 to 32
|
|
443
|
+
original_data[4, 28]
|
|
444
|
+
else
|
|
445
|
+
# Use defaults
|
|
446
|
+
[0, 0, 0, 0, 0, 0, 0].pack("N7")
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
data
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
# Convert unsigned 16-bit value to signed
|
|
453
|
+
#
|
|
454
|
+
# @param value [Integer] Unsigned 16-bit value
|
|
455
|
+
# @return [Integer] Signed 16-bit value
|
|
456
|
+
def to_signed_16(value)
|
|
457
|
+
value > 0x7FFF ? value - 0x10000 : value
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
end
|