fontisan 0.1.0 → 0.2.1
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 +672 -69
- data/Gemfile +1 -0
- data/LICENSE +5 -1
- data/README.adoc +1477 -297
- data/Rakefile +63 -41
- 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 +364 -4
- data/lib/fontisan/collection/builder.rb +341 -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 +317 -0
- data/lib/fontisan/collection/writer.rb +306 -0
- data/lib/fontisan/commands/base_command.rb +24 -1
- data/lib/fontisan/commands/convert_command.rb +218 -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 +286 -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 +203 -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 +79 -0
- data/lib/fontisan/converters/conversion_strategy.rb +96 -0
- data/lib/fontisan/converters/format_converter.rb +408 -0
- data/lib/fontisan/converters/outline_converter.rb +998 -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 +122 -15
- data/lib/fontisan/font_writer.rb +302 -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 +310 -0
- data/lib/fontisan/hints/postscript_hint_applier.rb +266 -0
- data/lib/fontisan/hints/postscript_hint_extractor.rb +354 -0
- data/lib/fontisan/hints/truetype_hint_applier.rb +117 -0
- data/lib/fontisan/hints/truetype_hint_extractor.rb +289 -0
- data/lib/fontisan/loading_modes.rb +115 -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 +405 -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 +321 -19
- data/lib/fontisan/open_type_font_extensions.rb +54 -0
- 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/pipeline/format_detector.rb +249 -0
- data/lib/fontisan/pipeline/output_writer.rb +154 -0
- data/lib/fontisan/pipeline/strategies/base_strategy.rb +75 -0
- data/lib/fontisan/pipeline/strategies/instance_strategy.rb +93 -0
- data/lib/fontisan/pipeline/strategies/named_strategy.rb +118 -0
- data/lib/fontisan/pipeline/strategies/preserve_strategy.rb +56 -0
- data/lib/fontisan/pipeline/transformation_pipeline.rb +411 -0
- data/lib/fontisan/pipeline/variation_resolver.rb +165 -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 +934 -0
- data/lib/fontisan/tables/cff/charstring_builder.rb +356 -0
- data/lib/fontisan/tables/cff/charstring_parser.rb +237 -0
- data/lib/fontisan/tables/cff/charstring_rebuilder.rb +172 -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 +257 -0
- data/lib/fontisan/tables/cff/encoding.rb +274 -0
- data/lib/fontisan/tables/cff/header.rb +102 -0
- data/lib/fontisan/tables/cff/hint_operation_injector.rb +207 -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/offset_recalculator.rb +70 -0
- data/lib/fontisan/tables/cff/private_dict.rb +284 -0
- data/lib/fontisan/tables/cff/private_dict_writer.rb +125 -0
- data/lib/fontisan/tables/cff/table_builder.rb +221 -0
- data/lib/fontisan/tables/cff/top_dict.rb +236 -0
- data/lib/fontisan/tables/cff.rb +489 -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/private_dict_blend_handler.rb +246 -0
- data/lib/fontisan/tables/cff2/region_matcher.rb +200 -0
- data/lib/fontisan/tables/cff2/table_builder.rb +574 -0
- data/lib/fontisan/tables/cff2/table_reader.rb +419 -0
- data/lib/fontisan/tables/cff2/variation_data_extractor.rb +212 -0
- data/lib/fontisan/tables/cff2.rb +346 -0
- data/lib/fontisan/tables/cvar.rb +203 -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 +231 -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 +321 -20
- data/lib/fontisan/true_type_font_extensions.rb +54 -0
- data/lib/fontisan/utilities/brotli_wrapper.rb +159 -0
- data/lib/fontisan/utilities/checksum_calculator.rb +60 -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/validation/variable_font_validator.rb +218 -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 +375 -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/instance_writer.rb +341 -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/tuple_variation_header.rb +51 -0
- data/lib/fontisan/variation/validator.rb +345 -0
- data/lib/fontisan/variation/variable_svg_generator.rb +268 -0
- data/lib/fontisan/variation/variation_context.rb +211 -0
- data/lib/fontisan/variation/variation_preserver.rb +288 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/version.rb.orig +9 -0
- data/lib/fontisan/woff2/directory.rb +257 -0
- data/lib/fontisan/woff2/glyf_transformer.rb +666 -0
- data/lib/fontisan/woff2/header.rb +101 -0
- data/lib/fontisan/woff2/hmtx_transformer.rb +164 -0
- data/lib/fontisan/woff2/table_transformer.rb +163 -0
- data/lib/fontisan/woff2_font.rb +717 -0
- data/lib/fontisan/woff_font.rb +488 -0
- data/lib/fontisan.rb +132 -0
- data/scripts/compare_stack_aware.rb +187 -0
- data/scripts/measure_optimization.rb +141 -0
- metadata +234 -4
data/lib/fontisan/cli.rb
CHANGED
|
@@ -25,12 +25,38 @@ module Fontisan
|
|
|
25
25
|
desc: "Suppress non-error output",
|
|
26
26
|
aliases: "-q"
|
|
27
27
|
|
|
28
|
-
desc "info
|
|
28
|
+
desc "info PATH", "Display font information"
|
|
29
29
|
# Extract and display comprehensive font metadata.
|
|
30
30
|
#
|
|
31
|
-
# @param
|
|
32
|
-
def info(
|
|
33
|
-
command = Commands::InfoCommand.new(
|
|
31
|
+
# @param path [String] Path to the font file or collection
|
|
32
|
+
def info(path)
|
|
33
|
+
command = Commands::InfoCommand.new(path, options)
|
|
34
|
+
info = command.run
|
|
35
|
+
output_result(info) unless options[:quiet]
|
|
36
|
+
rescue Errno::ENOENT => e
|
|
37
|
+
if options[:verbose]
|
|
38
|
+
raise
|
|
39
|
+
else
|
|
40
|
+
warn "File not found: #{path}" unless options[:quiet]
|
|
41
|
+
exit 1
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
desc "ls FILE", "List contents (fonts in collection or font summary)"
|
|
46
|
+
# List contents of font files with auto-detection.
|
|
47
|
+
#
|
|
48
|
+
# For collections (TTC/OTC): Lists all fonts in the collection
|
|
49
|
+
# For individual fonts (TTF/OTF): Shows quick font summary
|
|
50
|
+
#
|
|
51
|
+
# @param file [String] Path to the font or collection file
|
|
52
|
+
#
|
|
53
|
+
# @example List fonts in collection
|
|
54
|
+
# fontisan ls fonts.ttc
|
|
55
|
+
#
|
|
56
|
+
# @example Show font summary
|
|
57
|
+
# fontisan ls font.ttf
|
|
58
|
+
def ls(file)
|
|
59
|
+
command = Commands::LsCommand.new(file, options)
|
|
34
60
|
result = command.run
|
|
35
61
|
output_result(result)
|
|
36
62
|
rescue Errno::ENOENT, Error => e
|
|
@@ -125,6 +151,183 @@ module Fontisan
|
|
|
125
151
|
handle_error(e)
|
|
126
152
|
end
|
|
127
153
|
|
|
154
|
+
desc "subset FONT_FILE", "Subset a font to specific glyphs"
|
|
155
|
+
option :text, type: :string,
|
|
156
|
+
desc: "Text to subset (e.g., 'Hello World')",
|
|
157
|
+
aliases: "-t"
|
|
158
|
+
option :glyphs, type: :string,
|
|
159
|
+
desc: "Comma-separated glyph IDs (e.g., '0,1,65,66,67')",
|
|
160
|
+
aliases: "-g"
|
|
161
|
+
option :unicode, type: :string,
|
|
162
|
+
desc: "Comma-separated Unicode codepoints (e.g., 'U+0041,U+0042' or '0x41,0x42')",
|
|
163
|
+
aliases: "-u"
|
|
164
|
+
option :output, type: :string, required: true,
|
|
165
|
+
desc: "Output file path",
|
|
166
|
+
aliases: "-o"
|
|
167
|
+
option :profile, type: :string, default: "pdf",
|
|
168
|
+
desc: "Subsetting profile (pdf, web, minimal)",
|
|
169
|
+
aliases: "-p"
|
|
170
|
+
option :retain_gids, type: :boolean, default: false,
|
|
171
|
+
desc: "Retain original glyph IDs (leave gaps)"
|
|
172
|
+
option :drop_hints, type: :boolean, default: false,
|
|
173
|
+
desc: "Drop hinting instructions"
|
|
174
|
+
option :drop_names, type: :boolean, default: false,
|
|
175
|
+
desc: "Drop glyph names from post table"
|
|
176
|
+
option :unicode_ranges, type: :boolean, default: true,
|
|
177
|
+
desc: "Prune OS/2 Unicode ranges"
|
|
178
|
+
# Subset a font to specific glyphs.
|
|
179
|
+
#
|
|
180
|
+
# You must specify one of --text, --glyphs, or --unicode to define
|
|
181
|
+
# which glyphs to include in the subset.
|
|
182
|
+
#
|
|
183
|
+
# @param font_file [String] Path to the font file
|
|
184
|
+
def subset(font_file)
|
|
185
|
+
command = Commands::SubsetCommand.new(font_file, options)
|
|
186
|
+
result = command.run
|
|
187
|
+
|
|
188
|
+
unless options[:quiet]
|
|
189
|
+
puts "Subset font created:"
|
|
190
|
+
puts " Input: #{result[:input]}"
|
|
191
|
+
puts " Output: #{result[:output]}"
|
|
192
|
+
puts " Original glyphs: #{result[:original_glyphs]}"
|
|
193
|
+
puts " Subset glyphs: #{result[:subset_glyphs]}"
|
|
194
|
+
puts " Profile: #{result[:profile]}"
|
|
195
|
+
puts " Size: #{format_size(result[:size])}"
|
|
196
|
+
end
|
|
197
|
+
rescue Errno::ENOENT, Error => e
|
|
198
|
+
handle_error(e)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
desc "convert FONT_FILE", "Convert font to different format"
|
|
202
|
+
option :to, type: :string, required: true,
|
|
203
|
+
desc: "Target format (ttf, otf, woff, woff2)",
|
|
204
|
+
aliases: "-t"
|
|
205
|
+
option :output, type: :string, required: true,
|
|
206
|
+
desc: "Output file path",
|
|
207
|
+
aliases: "-o"
|
|
208
|
+
option :coordinates, type: :string,
|
|
209
|
+
desc: "Instance coordinates (e.g., wght=700,wdth=100)",
|
|
210
|
+
aliases: "-c"
|
|
211
|
+
option :instance_index, type: :numeric,
|
|
212
|
+
desc: "Named instance index",
|
|
213
|
+
aliases: "-n"
|
|
214
|
+
option :preserve_variation, type: :boolean,
|
|
215
|
+
desc: "Force variation preservation (auto-detected by default)"
|
|
216
|
+
option :no_validate, type: :boolean, default: false,
|
|
217
|
+
desc: "Skip output validation"
|
|
218
|
+
option :preserve_hints, type: :boolean, default: false,
|
|
219
|
+
desc: "Preserve rendering hints during conversion (TTF→OTF preservations may be limited)"
|
|
220
|
+
option :wght, type: :numeric,
|
|
221
|
+
desc: "Weight axis value (alternative to --coordinates)"
|
|
222
|
+
option :wdth, type: :numeric,
|
|
223
|
+
desc: "Width axis value (alternative to --coordinates)"
|
|
224
|
+
option :slnt, type: :numeric,
|
|
225
|
+
desc: "Slant axis value (alternative to --coordinates)"
|
|
226
|
+
option :ital, type: :numeric,
|
|
227
|
+
desc: "Italic axis value (alternative to --coordinates)"
|
|
228
|
+
option :opsz, type: :numeric,
|
|
229
|
+
desc: "Optical size axis value (alternative to --coordinates)"
|
|
230
|
+
# Convert a font to a different format using the universal transformation pipeline.
|
|
231
|
+
#
|
|
232
|
+
# Supported conversions:
|
|
233
|
+
# - TTF ↔ OTF: Outline format conversion
|
|
234
|
+
# - WOFF/WOFF2: Web font packaging
|
|
235
|
+
# - Variable fonts: Automatic variation preservation or instance generation
|
|
236
|
+
#
|
|
237
|
+
# Variable Font Operations:
|
|
238
|
+
# The pipeline automatically detects whether variation data can be preserved based on
|
|
239
|
+
# source and target formats. For same outline family (TTF→WOFF or OTF→WOFF2), variation
|
|
240
|
+
# is preserved automatically. For cross-family conversions (TTF↔OTF), an instance is
|
|
241
|
+
# generated unless --preserve-variation is explicitly set.
|
|
242
|
+
#
|
|
243
|
+
# Instance Generation:
|
|
244
|
+
# Use --coordinates to specify exact axis values (e.g., wght=700,wdth=100) or
|
|
245
|
+
# --instance-index to use a named instance. Individual axis options (--wght, --wdth)
|
|
246
|
+
# are also supported for convenience.
|
|
247
|
+
#
|
|
248
|
+
# @param font_file [String] Path to the font file
|
|
249
|
+
#
|
|
250
|
+
# @example Convert TTF to OTF
|
|
251
|
+
# fontisan convert font.ttf --to otf --output font.otf
|
|
252
|
+
#
|
|
253
|
+
# @example Generate bold instance at specific coordinates
|
|
254
|
+
# fontisan convert variable.ttf --to ttf --output bold.ttf --coordinates "wght=700,wdth=100"
|
|
255
|
+
#
|
|
256
|
+
# @example Generate bold instance using individual axis options
|
|
257
|
+
# fontisan convert variable.ttf --to ttf --output bold.ttf --wght 700
|
|
258
|
+
#
|
|
259
|
+
# @example Use named instance
|
|
260
|
+
# fontisan convert variable.ttf --to woff2 --output bold.woff2 --instance-index 0
|
|
261
|
+
#
|
|
262
|
+
# @example Force variation preservation (if compatible)
|
|
263
|
+
# fontisan convert variable.ttf --to woff2 --output variable.woff2 --preserve-variation
|
|
264
|
+
#
|
|
265
|
+
# @example Convert without validation
|
|
266
|
+
# fontisan convert font.ttf --to otf --output font.otf --no-validate
|
|
267
|
+
def convert(font_file)
|
|
268
|
+
# Build instance coordinates from axis options
|
|
269
|
+
instance_coords = build_instance_coordinates(options)
|
|
270
|
+
|
|
271
|
+
# Merge coordinates into options
|
|
272
|
+
convert_options = options.to_h.dup
|
|
273
|
+
convert_options[:instance_coordinates] = instance_coords if instance_coords.any?
|
|
274
|
+
|
|
275
|
+
command = Commands::ConvertCommand.new(font_file, convert_options)
|
|
276
|
+
command.run
|
|
277
|
+
rescue Errno::ENOENT, Error => e
|
|
278
|
+
handle_error(e)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
desc "instance FONT_FILE",
|
|
282
|
+
"Generate static font instance from variable font"
|
|
283
|
+
option :output, type: :string,
|
|
284
|
+
desc: "Output file path",
|
|
285
|
+
aliases: "-o"
|
|
286
|
+
option :wght, type: :numeric,
|
|
287
|
+
desc: "Weight axis value"
|
|
288
|
+
option :wdth, type: :numeric,
|
|
289
|
+
desc: "Width axis value"
|
|
290
|
+
option :slnt, type: :numeric,
|
|
291
|
+
desc: "Slant axis value"
|
|
292
|
+
option :ital, type: :numeric,
|
|
293
|
+
desc: "Italic axis value"
|
|
294
|
+
option :opsz, type: :numeric,
|
|
295
|
+
desc: "Optical size axis value"
|
|
296
|
+
option :named_instance, type: :string,
|
|
297
|
+
desc: "Use named instance (e.g., 'Bold', 'Light')",
|
|
298
|
+
aliases: "-n"
|
|
299
|
+
option :list_instances, type: :boolean, default: false,
|
|
300
|
+
desc: "List available named instances",
|
|
301
|
+
aliases: "-l"
|
|
302
|
+
option :to, type: :string,
|
|
303
|
+
desc: "Convert to format (ttf, otf, woff, woff2, svg)",
|
|
304
|
+
aliases: "-t"
|
|
305
|
+
# Generate static font instance from variable font.
|
|
306
|
+
#
|
|
307
|
+
# You can specify axis coordinates using --wght, --wdth, etc., or use
|
|
308
|
+
# a predefined named instance with --named-instance. Use --list-instances
|
|
309
|
+
# to see available named instances.
|
|
310
|
+
#
|
|
311
|
+
# @param font_file [String] Path to the variable font file
|
|
312
|
+
#
|
|
313
|
+
# @example Generate bold instance
|
|
314
|
+
# fontisan instance variable.ttf --wght=700 --output=bold.ttf
|
|
315
|
+
#
|
|
316
|
+
# @example Use named instance
|
|
317
|
+
# fontisan instance variable.ttf --named-instance="Bold" --output=bold.ttf
|
|
318
|
+
#
|
|
319
|
+
# @example Instance with format conversion
|
|
320
|
+
# fontisan instance variable.ttf --wght=700 --to=woff2 --output=bold.woff2
|
|
321
|
+
#
|
|
322
|
+
# @example List available instances
|
|
323
|
+
# fontisan instance variable.ttf --list-instances
|
|
324
|
+
def instance(font_file)
|
|
325
|
+
command = Commands::InstanceCommand.new
|
|
326
|
+
command.execute(font_file, options)
|
|
327
|
+
rescue Errno::ENOENT, Error => e
|
|
328
|
+
handle_error(e)
|
|
329
|
+
end
|
|
330
|
+
|
|
128
331
|
desc "dump-table FONT_FILE TABLE_TAG", "Dump raw table data to stdout"
|
|
129
332
|
# Dump raw binary table data to stdout.
|
|
130
333
|
#
|
|
@@ -141,14 +344,157 @@ module Fontisan
|
|
|
141
344
|
handle_error(e)
|
|
142
345
|
end
|
|
143
346
|
|
|
347
|
+
desc "validate FONT_FILE", "Validate font file structure and checksums"
|
|
348
|
+
option :verbose, type: :boolean, default: false,
|
|
349
|
+
desc: "Show detailed validation information"
|
|
350
|
+
def validate(font_file)
|
|
351
|
+
command = Commands::ValidateCommand.new(font_file, verbose: options[:verbose])
|
|
352
|
+
exit command.run
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
desc "export FONT_FILE", "Export font to TTX/YAML/JSON format"
|
|
356
|
+
option :output, type: :string,
|
|
357
|
+
desc: "Output file path (default: stdout)",
|
|
358
|
+
aliases: "-o"
|
|
359
|
+
option :format, type: :string, default: "yaml",
|
|
360
|
+
desc: "Export format (yaml, json, ttx)",
|
|
361
|
+
aliases: "-f"
|
|
362
|
+
option :tables, type: :array,
|
|
363
|
+
desc: "Specific tables to export",
|
|
364
|
+
aliases: "-t"
|
|
365
|
+
option :binary_format, type: :string, default: "hex",
|
|
366
|
+
desc: "Binary encoding (hex, base64)",
|
|
367
|
+
aliases: "-b"
|
|
368
|
+
|
|
369
|
+
def export(font_file)
|
|
370
|
+
command = Commands::ExportCommand.new(
|
|
371
|
+
font_file,
|
|
372
|
+
output: options[:output],
|
|
373
|
+
format: options[:format].to_sym,
|
|
374
|
+
tables: options[:tables],
|
|
375
|
+
binary_format: options[:binary_format].to_sym,
|
|
376
|
+
)
|
|
377
|
+
exit command.run
|
|
378
|
+
end
|
|
379
|
+
|
|
144
380
|
desc "version", "Display version information"
|
|
145
381
|
# Display the Fontisan version.
|
|
146
382
|
def version
|
|
147
383
|
puts "Fontisan version #{Fontisan::VERSION}"
|
|
148
384
|
end
|
|
149
385
|
|
|
386
|
+
desc "unpack FONT_FILE", "Unpack fonts from TTC/OTC collection"
|
|
387
|
+
option :output_dir, type: :string, required: true,
|
|
388
|
+
desc: "Output directory for extracted fonts",
|
|
389
|
+
aliases: "-d"
|
|
390
|
+
option :font_index, type: :numeric,
|
|
391
|
+
desc: "Extract specific font by index (default: extract all)",
|
|
392
|
+
aliases: "-i"
|
|
393
|
+
option :format, type: :string,
|
|
394
|
+
desc: "Output format (ttf, otf, woff, woff2)",
|
|
395
|
+
aliases: "-f"
|
|
396
|
+
option :prefix, type: :string,
|
|
397
|
+
desc: "Filename prefix for extracted fonts",
|
|
398
|
+
aliases: "-p"
|
|
399
|
+
# Extract individual fonts from a TTC (TrueType Collection) or OTC (OpenType Collection) file.
|
|
400
|
+
#
|
|
401
|
+
# This command unpacks fonts from collection files, optionally converting them
|
|
402
|
+
# to different formats during extraction.
|
|
403
|
+
#
|
|
404
|
+
# @param font_file [String] Path to the TTC/OTC collection file
|
|
405
|
+
#
|
|
406
|
+
# @example Extract all fonts to directory
|
|
407
|
+
# fontisan unpack family.ttc --output-dir extracted/
|
|
408
|
+
#
|
|
409
|
+
# @example Extract specific font by index
|
|
410
|
+
# fontisan unpack family.ttc --output-dir extracted/ --font-index 0
|
|
411
|
+
#
|
|
412
|
+
# @example Extract with format conversion
|
|
413
|
+
# fontisan unpack family.ttc --output-dir extracted/ --format woff2
|
|
414
|
+
#
|
|
415
|
+
# @example Extract with custom prefix
|
|
416
|
+
# fontisan unpack family.ttc --output-dir extracted/ --prefix "NotoSans"
|
|
417
|
+
def unpack(font_file)
|
|
418
|
+
command = Commands::UnpackCommand.new(font_file, options)
|
|
419
|
+
result = command.run
|
|
420
|
+
|
|
421
|
+
unless options[:quiet]
|
|
422
|
+
puts "Collection unpacked successfully:"
|
|
423
|
+
puts " Input: #{result[:collection]}"
|
|
424
|
+
puts " Output directory: #{result[:output_dir]}"
|
|
425
|
+
puts " Fonts extracted: #{result[:fonts_extracted]}/#{result[:num_fonts]}"
|
|
426
|
+
result[:extracted_files].each do |file|
|
|
427
|
+
size = File.size(file)
|
|
428
|
+
puts " - #{File.basename(file)} (#{format_size(size)})"
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
rescue Errno::ENOENT, Error => e
|
|
432
|
+
handle_error(e)
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
desc "pack FONT_FILES...", "Pack multiple fonts into TTC/OTC collection"
|
|
436
|
+
option :output, type: :string, required: true,
|
|
437
|
+
desc: "Output collection file path",
|
|
438
|
+
aliases: "-o"
|
|
439
|
+
option :format, type: :string, default: "ttc",
|
|
440
|
+
desc: "Collection format (ttc, otc)",
|
|
441
|
+
aliases: "-f"
|
|
442
|
+
option :optimize, type: :boolean, default: true,
|
|
443
|
+
desc: "Enable table sharing optimization",
|
|
444
|
+
aliases: "--optimize"
|
|
445
|
+
option :analyze, type: :boolean, default: false,
|
|
446
|
+
desc: "Show analysis report before building",
|
|
447
|
+
aliases: "--analyze"
|
|
448
|
+
# Create a TTC (TrueType Collection) or OTC (OpenType Collection) from multiple font files.
|
|
449
|
+
#
|
|
450
|
+
# This command combines multiple fonts into a single collection file with
|
|
451
|
+
# shared table deduplication to save space. It supports both TTC and OTC formats.
|
|
452
|
+
#
|
|
453
|
+
# @param font_files [Array<String>] Paths to input font files (minimum 2 required)
|
|
454
|
+
#
|
|
455
|
+
# @example Pack fonts into TTC
|
|
456
|
+
# fontisan pack font1.ttf font2.ttf font3.ttf --output family.ttc
|
|
457
|
+
#
|
|
458
|
+
# @example Pack into OTC with analysis
|
|
459
|
+
# fontisan pack Regular.otf Bold.otf Italic.otf --output family.otc --analyze
|
|
460
|
+
#
|
|
461
|
+
# @example Pack without optimization
|
|
462
|
+
# fontisan pack font1.ttf font2.ttf --output collection.ttc --no-optimize
|
|
463
|
+
def pack(*font_files)
|
|
464
|
+
command = Commands::PackCommand.new(font_files, options)
|
|
465
|
+
result = command.run
|
|
466
|
+
|
|
467
|
+
unless options[:quiet]
|
|
468
|
+
puts "Collection created successfully:"
|
|
469
|
+
puts " Output: #{result[:output]}"
|
|
470
|
+
puts " Format: #{result[:format].upcase}"
|
|
471
|
+
puts " Fonts: #{result[:num_fonts]}"
|
|
472
|
+
puts " Size: #{format_size(result[:output_size])}"
|
|
473
|
+
if result[:space_savings].positive?
|
|
474
|
+
puts " Space saved: #{format_size(result[:space_savings])}"
|
|
475
|
+
puts " Sharing: #{result[:sharing_percentage]}%"
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
rescue Errno::ENOENT, Error => e
|
|
479
|
+
handle_error(e)
|
|
480
|
+
end
|
|
481
|
+
|
|
150
482
|
private
|
|
151
483
|
|
|
484
|
+
# Build instance coordinates from CLI axis options
|
|
485
|
+
#
|
|
486
|
+
# @param options [Hash] CLI options
|
|
487
|
+
# @return [Hash] Coordinates hash
|
|
488
|
+
def build_instance_coordinates(options)
|
|
489
|
+
coords = {}
|
|
490
|
+
coords["wght"] = options[:wght].to_f if options[:wght]
|
|
491
|
+
coords["wdth"] = options[:wdth].to_f if options[:wdth]
|
|
492
|
+
coords["slnt"] = options[:slnt].to_f if options[:slnt]
|
|
493
|
+
coords["ital"] = options[:ital].to_f if options[:ital]
|
|
494
|
+
coords["opsz"] = options[:opsz].to_f if options[:opsz]
|
|
495
|
+
coords
|
|
496
|
+
end
|
|
497
|
+
|
|
152
498
|
# Output the result in the requested format.
|
|
153
499
|
#
|
|
154
500
|
# @param result [Object] The result object to output
|
|
@@ -174,6 +520,20 @@ module Fontisan
|
|
|
174
520
|
formatter.format(result)
|
|
175
521
|
end
|
|
176
522
|
|
|
523
|
+
# Format file size in human-readable form
|
|
524
|
+
#
|
|
525
|
+
# @param size [Integer] Size in bytes
|
|
526
|
+
# @return [String] Formatted size
|
|
527
|
+
def format_size(size)
|
|
528
|
+
if size < 1024
|
|
529
|
+
"#{size} bytes"
|
|
530
|
+
elsif size < 1024 * 1024
|
|
531
|
+
"#{(size / 1024.0).round(2)} KB"
|
|
532
|
+
else
|
|
533
|
+
"#{(size / (1024.0 * 1024)).round(2)} MB"
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
|
|
177
537
|
# Handle errors based on verbosity settings.
|
|
178
538
|
#
|
|
179
539
|
# @param error [Error, Errno::ENOENT] The error to handle
|