fontisan 0.2.16 → 0.2.22
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 +14 -90
- data/Gemfile +6 -3
- data/README.adoc +257 -1
- data/docs/.vitepress/config.ts +68 -8
- data/docs/.vitepress/theme/style.css +570 -272
- data/docs/CONVERSION_GUIDE.adoc +31 -8
- data/docs/EXTRACT_TTC_MIGRATION.md +1 -1
- data/docs/WOFF_WOFF2_FORMATS.adoc +53 -0
- data/docs/api/conversion-options.md +37 -14
- data/docs/api/font-loader.md +21 -15
- data/docs/cli/audit.md +337 -0
- data/docs/cli/convert.md +20 -1
- data/docs/cli/index.md +31 -0
- data/docs/guide/color.md +1 -1
- data/docs/guide/conversion/options.md +32 -3
- data/docs/guide/conversion/ttf-otf.md +1 -1
- data/docs/guide/conversion/type1.md +1 -1
- data/docs/guide/conversion/web.md +91 -32
- data/docs/guide/conversion.md +6 -5
- data/docs/guide/formats/woff.md +35 -11
- data/docs/guide/index.md +2 -2
- data/docs/guide/migrations/extract-ttc.md +1 -1
- data/docs/guide/quick-start.md +4 -4
- data/docs/guide/type1.md +4 -4
- data/docs/guide/woff.md +19 -17
- data/docs/index.md +2 -0
- data/docs/lychee.toml +5 -1
- data/docs/package.json +1 -1
- data/docs/public/robots.txt +4 -0
- data/docs/scripts/post-build.mjs +81 -0
- data/lib/fontisan/audit/codepoint_range_coalescer.rb +41 -0
- data/lib/fontisan/audit/context.rb +122 -0
- data/lib/fontisan/audit/differ.rb +124 -0
- data/lib/fontisan/audit/extractors/aggregations.rb +54 -0
- data/lib/fontisan/audit/extractors/base.rb +26 -0
- data/lib/fontisan/audit/extractors/color_capabilities.rb +141 -0
- data/lib/fontisan/audit/extractors/coverage.rb +48 -0
- data/lib/fontisan/audit/extractors/hinting.rb +197 -0
- data/lib/fontisan/audit/extractors/identity.rb +52 -0
- data/lib/fontisan/audit/extractors/language_coverage.rb +37 -0
- data/lib/fontisan/audit/extractors/licensing.rb +79 -0
- data/lib/fontisan/audit/extractors/metrics.rb +103 -0
- data/lib/fontisan/audit/extractors/opentype_layout.rb +69 -0
- data/lib/fontisan/audit/extractors/provenance.rb +29 -0
- data/lib/fontisan/audit/extractors/style.rb +32 -0
- data/lib/fontisan/audit/extractors/variation_detail.rb +99 -0
- data/lib/fontisan/audit/extractors.rb +27 -0
- data/lib/fontisan/audit/library_aggregator.rb +83 -0
- data/lib/fontisan/audit/library_auditor.rb +90 -0
- data/lib/fontisan/audit/registry.rb +60 -0
- data/lib/fontisan/audit/style_extractor.rb +80 -0
- data/lib/fontisan/audit.rb +20 -0
- data/lib/fontisan/base_collection.rb +23 -9
- data/lib/fontisan/binary/structures.rb +0 -2
- data/lib/fontisan/binary.rb +11 -0
- data/lib/fontisan/cldr/aggregator.rb +33 -0
- data/lib/fontisan/cldr/cache_manager.rb +110 -0
- data/lib/fontisan/cldr/config.rb +59 -0
- data/lib/fontisan/cldr/download_error.rb +9 -0
- data/lib/fontisan/cldr/downloader.rb +79 -0
- data/lib/fontisan/cldr/error.rb +8 -0
- data/lib/fontisan/cldr/index.rb +64 -0
- data/lib/fontisan/cldr/index_builder.rb +72 -0
- data/lib/fontisan/cldr/unicode_set_parser.rb +172 -0
- data/lib/fontisan/cldr/unknown_version_error.rb +9 -0
- data/lib/fontisan/cldr/version_resolver.rb +91 -0
- data/lib/fontisan/cldr.rb +23 -0
- data/lib/fontisan/cli/cldr_cli.rb +85 -0
- data/lib/fontisan/cli/ucd_cli.rb +97 -0
- data/lib/fontisan/cli.rb +201 -2
- data/lib/fontisan/collection/builder.rb +0 -4
- data/lib/fontisan/collection/dfont_builder.rb +0 -4
- data/lib/fontisan/collection/shared_logic.rb +0 -2
- data/lib/fontisan/collection/writer.rb +0 -3
- data/lib/fontisan/collection.rb +15 -0
- data/lib/fontisan/commands/audit_command.rb +123 -0
- data/lib/fontisan/commands/audit_compare_command.rb +66 -0
- data/lib/fontisan/commands/audit_library_command.rb +46 -0
- data/lib/fontisan/commands/base_command.rb +0 -3
- data/lib/fontisan/commands/convert_command.rb +25 -20
- data/lib/fontisan/commands/dump_table_command.rb +0 -3
- data/lib/fontisan/commands/export_command.rb +0 -4
- data/lib/fontisan/commands/features_command.rb +0 -3
- data/lib/fontisan/commands/instance_command.rb +0 -5
- data/lib/fontisan/commands/ls_command.rb +0 -6
- data/lib/fontisan/commands/optical_size_command.rb +0 -3
- data/lib/fontisan/commands/pack_command.rb +0 -5
- data/lib/fontisan/commands/scripts_command.rb +0 -2
- data/lib/fontisan/commands/subset_command.rb +0 -3
- data/lib/fontisan/commands/unicode_command.rb +0 -3
- data/lib/fontisan/commands/unpack_command.rb +0 -7
- data/lib/fontisan/commands/validate_command.rb +0 -8
- data/lib/fontisan/commands/variable_command.rb +0 -3
- data/lib/fontisan/commands.rb +29 -0
- data/lib/fontisan/config/cldr.yml +22 -0
- data/lib/fontisan/config/conversion_matrix.yml +38 -0
- data/lib/fontisan/config/ucd.yml +23 -0
- data/lib/fontisan/constants.rb +48 -6
- data/lib/fontisan/conversion_options.rb +30 -19
- data/lib/fontisan/converters/cff_table_builder.rb +0 -3
- data/lib/fontisan/converters/collection_converter.rb +0 -8
- data/lib/fontisan/converters/conversion_strategy.rb +161 -46
- data/lib/fontisan/converters/format_converter.rb +143 -32
- data/lib/fontisan/converters/glyf_table_builder.rb +0 -2
- data/lib/fontisan/converters/outline_converter.rb +0 -19
- data/lib/fontisan/converters/outline_extraction.rb +0 -5
- data/lib/fontisan/converters/outline_optimizer.rb +0 -5
- data/lib/fontisan/converters/svg_generator.rb +0 -4
- data/lib/fontisan/converters/table_copier.rb +0 -2
- data/lib/fontisan/converters/type1_converter.rb +0 -11
- data/lib/fontisan/converters/woff2_encoder.rb +49 -20
- data/lib/fontisan/converters/woff_writer.rb +211 -282
- data/lib/fontisan/converters.rb +21 -0
- data/lib/fontisan/dfont_collection.rb +29 -10
- data/lib/fontisan/export/exporter.rb +0 -6
- data/lib/fontisan/export/transformers/font_to_ttx.rb +0 -9
- data/lib/fontisan/export/transformers/head_transformer.rb +0 -2
- data/lib/fontisan/export/transformers/hhea_transformer.rb +0 -2
- data/lib/fontisan/export/transformers/maxp_transformer.rb +0 -2
- data/lib/fontisan/export/transformers/name_transformer.rb +0 -2
- data/lib/fontisan/export/transformers/os2_transformer.rb +0 -2
- data/lib/fontisan/export/transformers/post_transformer.rb +0 -2
- data/lib/fontisan/export/transformers.rb +17 -0
- data/lib/fontisan/export.rb +13 -0
- data/lib/fontisan/font_loader.rb +189 -328
- data/lib/fontisan/font_writer.rb +0 -1
- data/lib/fontisan/formatters/audit_diff_text_renderer.rb +122 -0
- data/lib/fontisan/formatters/audit_text_renderer.rb +324 -0
- data/lib/fontisan/formatters/library_summary_text_renderer.rb +99 -0
- data/lib/fontisan/formatters/text_formatter.rb +6 -0
- data/lib/fontisan/formatters.rb +12 -0
- data/lib/fontisan/hints/hint_converter.rb +0 -1
- data/lib/fontisan/hints/postscript_hint_applier.rb +0 -9
- data/lib/fontisan/hints/postscript_hint_extractor.rb +0 -2
- data/lib/fontisan/hints/truetype_hint_extractor.rb +0 -2
- data/lib/fontisan/hints.rb +16 -0
- data/lib/fontisan/metrics_calculator.rb +0 -2
- data/lib/fontisan/models/all_scripts_features_info.rb +0 -1
- data/lib/fontisan/models/audit/audit_axis.rb +30 -0
- data/lib/fontisan/models/audit/audit_block.rb +32 -0
- data/lib/fontisan/models/audit/audit_diff.rb +77 -0
- data/lib/fontisan/models/audit/audit_report.rb +153 -0
- data/lib/fontisan/models/audit/codepoint_range.rb +40 -0
- data/lib/fontisan/models/audit/codepoint_set_diff.rb +34 -0
- data/lib/fontisan/models/audit/color_capabilities.rb +93 -0
- data/lib/fontisan/models/audit/duplicate_group.rb +23 -0
- data/lib/fontisan/models/audit/embedding_type.rb +76 -0
- data/lib/fontisan/models/audit/field_change.rb +28 -0
- data/lib/fontisan/models/audit/fs_selection_flags.rb +61 -0
- data/lib/fontisan/models/audit/gasp_range.rb +63 -0
- data/lib/fontisan/models/audit/hinting.rb +93 -0
- data/lib/fontisan/models/audit/library_summary.rb +40 -0
- data/lib/fontisan/models/audit/licensing.rb +48 -0
- data/lib/fontisan/models/audit/metrics.rb +111 -0
- data/lib/fontisan/models/audit/named_instance.rb +41 -0
- data/lib/fontisan/models/audit/opentype_layout.rb +40 -0
- data/lib/fontisan/models/audit/script_coverage_row.rb +26 -0
- data/lib/fontisan/models/audit/script_features.rb +28 -0
- data/lib/fontisan/models/audit/variation_detail.rb +44 -0
- data/lib/fontisan/models/audit.rb +33 -0
- data/lib/fontisan/models/cldr/language_coverage.rb +31 -0
- data/lib/fontisan/models/cldr.rb +12 -0
- data/lib/fontisan/models/collection_brief_info.rb +0 -1
- data/lib/fontisan/models/collection_info.rb +0 -2
- data/lib/fontisan/models/collection_list_info.rb +0 -1
- data/lib/fontisan/models/collection_validation_report.rb +0 -2
- data/lib/fontisan/models/color_glyph.rb +0 -1
- data/lib/fontisan/models/font_report.rb +0 -1
- data/lib/fontisan/models/ttx/tables.rb +21 -0
- data/lib/fontisan/models/ttx/ttfont.rb +0 -8
- data/lib/fontisan/models/ttx.rb +14 -0
- data/lib/fontisan/models/ucd/ucd.rb +38 -0
- data/lib/fontisan/models/ucd/ucd_char.rb +67 -0
- data/lib/fontisan/models/ucd.rb +19 -0
- data/lib/fontisan/models.rb +47 -0
- data/lib/fontisan/open_type_collection.rb +6 -5
- data/lib/fontisan/open_type_font.rb +8 -2
- data/lib/fontisan/open_type_font_extensions.rb +9 -9
- data/lib/fontisan/optimizers/pattern_analyzer.rb +0 -1
- data/lib/fontisan/optimizers.rb +14 -0
- data/lib/fontisan/outline_extractor.rb +0 -2
- data/lib/fontisan/parsers/dfont_parser.rb +0 -1
- data/lib/fontisan/parsers.rb +10 -0
- data/lib/fontisan/pipeline/format_detector.rb +29 -102
- data/lib/fontisan/pipeline/output_writer.rb +11 -9
- data/lib/fontisan/pipeline/strategies/instance_strategy.rb +0 -4
- data/lib/fontisan/pipeline/strategies/named_strategy.rb +0 -4
- data/lib/fontisan/pipeline/strategies/preserve_strategy.rb +0 -2
- data/lib/fontisan/pipeline/strategies.rb +14 -0
- data/lib/fontisan/pipeline/transformation_pipeline.rb +0 -7
- data/lib/fontisan/pipeline/variation_resolver.rb +0 -7
- data/lib/fontisan/pipeline.rb +13 -0
- data/lib/fontisan/sfnt_font.rb +29 -14
- data/lib/fontisan/sfnt_table.rb +0 -4
- data/lib/fontisan/subset/builder.rb +0 -6
- data/lib/fontisan/subset.rb +13 -0
- data/lib/fontisan/svg/font_generator.rb +0 -4
- data/lib/fontisan/svg/glyph_generator.rb +0 -2
- data/lib/fontisan/svg.rb +12 -0
- data/lib/fontisan/tables/cbdt.rb +0 -1
- data/lib/fontisan/tables/cblc.rb +0 -1
- data/lib/fontisan/tables/cff/charset.rb +0 -1
- data/lib/fontisan/tables/cff/charstring.rb +0 -1
- data/lib/fontisan/tables/cff/charstring_rebuilder.rb +0 -4
- data/lib/fontisan/tables/cff/charstrings_index.rb +0 -3
- data/lib/fontisan/tables/cff/dict.rb +0 -1
- data/lib/fontisan/tables/cff/encoding.rb +0 -1
- data/lib/fontisan/tables/cff/header.rb +0 -2
- data/lib/fontisan/tables/cff/hint_operation_injector.rb +0 -2
- data/lib/fontisan/tables/cff/index.rb +0 -1
- data/lib/fontisan/tables/cff/private_dict.rb +0 -2
- data/lib/fontisan/tables/cff/private_dict_writer.rb +0 -2
- data/lib/fontisan/tables/cff/table_builder.rb +0 -6
- data/lib/fontisan/tables/cff/top_dict.rb +0 -2
- data/lib/fontisan/tables/cff.rb +22 -15
- data/lib/fontisan/tables/cff2/charstring_parser.rb +0 -2
- data/lib/fontisan/tables/cff2/table_builder.rb +0 -11
- data/lib/fontisan/tables/cff2/table_reader.rb +0 -2
- data/lib/fontisan/tables/cff2.rb +13 -14
- data/lib/fontisan/tables/cmap.rb +24 -2
- data/lib/fontisan/tables/cmap_table.rb +0 -3
- data/lib/fontisan/tables/colr.rb +0 -1
- data/lib/fontisan/tables/cpal.rb +0 -1
- data/lib/fontisan/tables/cvar.rb +0 -2
- data/lib/fontisan/tables/fvar.rb +0 -1
- data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +0 -2
- data/lib/fontisan/tables/glyf/glyph_builder.rb +0 -3
- data/lib/fontisan/tables/glyf.rb +0 -6
- data/lib/fontisan/tables/glyf_table.rb +0 -3
- data/lib/fontisan/tables/gpos.rb +0 -2
- data/lib/fontisan/tables/gsub.rb +0 -2
- data/lib/fontisan/tables/gvar.rb +0 -2
- data/lib/fontisan/tables/head.rb +0 -2
- data/lib/fontisan/tables/head_table.rb +0 -3
- data/lib/fontisan/tables/hhea.rb +0 -2
- data/lib/fontisan/tables/hhea_table.rb +0 -3
- data/lib/fontisan/tables/hmtx.rb +0 -2
- data/lib/fontisan/tables/hmtx_table.rb +0 -3
- data/lib/fontisan/tables/hvar.rb +0 -3
- data/lib/fontisan/tables/loca.rb +0 -2
- data/lib/fontisan/tables/loca_table.rb +0 -3
- data/lib/fontisan/tables/maxp.rb +0 -2
- data/lib/fontisan/tables/maxp_table.rb +0 -3
- data/lib/fontisan/tables/mvar.rb +0 -3
- data/lib/fontisan/tables/name.rb +0 -2
- data/lib/fontisan/tables/name_table.rb +0 -3
- data/lib/fontisan/tables/os2_table.rb +0 -3
- data/lib/fontisan/tables/post_table.rb +0 -3
- data/lib/fontisan/tables/sbix.rb +0 -1
- data/lib/fontisan/tables/svg.rb +0 -1
- data/lib/fontisan/tables/variation_common.rb +0 -1
- data/lib/fontisan/tables/vvar.rb +0 -3
- data/lib/fontisan/tables.rb +54 -0
- data/lib/fontisan/true_type_collection.rb +6 -14
- data/lib/fontisan/true_type_font.rb +8 -2
- data/lib/fontisan/true_type_font_extensions.rb +9 -9
- data/lib/fontisan/type1/afm_generator.rb +0 -4
- data/lib/fontisan/type1/conversion_options.rb +0 -2
- data/lib/fontisan/type1/encodings.rb +0 -2
- data/lib/fontisan/type1/generator.rb +0 -8
- data/lib/fontisan/type1/pfa_generator.rb +0 -3
- data/lib/fontisan/type1/pfb_generator.rb +0 -5
- data/lib/fontisan/type1/pfm_generator.rb +0 -4
- data/lib/fontisan/type1.rb +42 -69
- data/lib/fontisan/type1_font.rb +40 -11
- data/lib/fontisan/ucd/aggregator.rb +73 -0
- data/lib/fontisan/ucd/cache_manager.rb +111 -0
- data/lib/fontisan/ucd/config.rb +59 -0
- data/lib/fontisan/ucd/download_error.rb +9 -0
- data/lib/fontisan/ucd/downloader.rb +88 -0
- data/lib/fontisan/ucd/error.rb +8 -0
- data/lib/fontisan/ucd/index.rb +103 -0
- data/lib/fontisan/ucd/index_builder.rb +107 -0
- data/lib/fontisan/ucd/range_entry.rb +56 -0
- data/lib/fontisan/ucd/unknown_version_error.rb +9 -0
- data/lib/fontisan/ucd/version_resolver.rb +79 -0
- data/lib/fontisan/ucd.rb +23 -0
- data/lib/fontisan/utilities/checksum_calculator.rb +0 -1
- data/lib/fontisan/utilities.rb +10 -0
- data/lib/fontisan/utils.rb +10 -0
- data/lib/fontisan/validation/collection_validator.rb +0 -2
- data/lib/fontisan/validation.rb +9 -0
- data/lib/fontisan/validators/basic_validator.rb +0 -2
- data/lib/fontisan/validators/font_book_validator.rb +0 -2
- data/lib/fontisan/validators/opentype_validator.rb +0 -2
- data/lib/fontisan/validators/profile_loader.rb +0 -5
- data/lib/fontisan/validators/validator.rb +0 -2
- data/lib/fontisan/validators/web_font_validator.rb +0 -2
- data/lib/fontisan/validators.rb +14 -0
- data/lib/fontisan/variable/delta_applicator.rb +0 -4
- data/lib/fontisan/variable/instancer.rb +0 -3
- data/lib/fontisan/variable/static_font_builder.rb +0 -3
- data/lib/fontisan/variable.rb +16 -0
- data/lib/fontisan/variation/blend_applier.rb +0 -2
- data/lib/fontisan/variation/cache.rb +0 -2
- data/lib/fontisan/variation/converter.rb +0 -3
- data/lib/fontisan/variation/data_extractor.rb +0 -2
- data/lib/fontisan/variation/delta_applier.rb +0 -5
- data/lib/fontisan/variation/inspector.rb +0 -1
- data/lib/fontisan/variation/instance_generator.rb +0 -6
- data/lib/fontisan/variation/instance_writer.rb +0 -5
- data/lib/fontisan/variation/metrics_adjuster.rb +0 -4
- data/lib/fontisan/variation/optimizer.rb +0 -3
- data/lib/fontisan/variation/parallel_generator.rb +0 -3
- data/lib/fontisan/variation/subsetter.rb +0 -4
- data/lib/fontisan/variation/tuple_variation_header.rb +0 -2
- data/lib/fontisan/variation/variable_svg_generator.rb +0 -3
- data/lib/fontisan/variation/variation_context.rb +0 -3
- data/lib/fontisan/variation/variation_preserver.rb +0 -3
- data/lib/fontisan/variation.rb +31 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2.rb +13 -0
- data/lib/fontisan/woff2_font.rb +31 -9
- data/lib/fontisan/woff_font.rb +31 -2
- data/lib/fontisan.rb +124 -196
- metadata +128 -7
- data/fontisan.gemspec +0 -47
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
# Thor subcommand for managing the local UCD (Unicode Character
|
|
7
|
+
# Database) cache used by `fontisan audit`.
|
|
8
|
+
#
|
|
9
|
+
# fontisan ucd download [VERSION] fetch + index UCDXML
|
|
10
|
+
# fontisan ucd status show what's cached
|
|
11
|
+
# fontisan ucd path [VERSION] print local cache path
|
|
12
|
+
# fontisan ucd list list known versions
|
|
13
|
+
# fontisan ucd remove VERSION delete a cached version
|
|
14
|
+
#
|
|
15
|
+
# With no arguments, `download` resolves the configured default version
|
|
16
|
+
# (see lib/fontisan/config/ucd.yml).
|
|
17
|
+
class UcdCli < Thor
|
|
18
|
+
desc "download [VERSION]",
|
|
19
|
+
"Download and index UCDXML (default: configured default version)"
|
|
20
|
+
option :force, type: :boolean, default: false,
|
|
21
|
+
desc: "Re-download even if already cached"
|
|
22
|
+
option :latest, type: :boolean, default: false,
|
|
23
|
+
desc: "Probe unicode.org for the latest version"
|
|
24
|
+
# Download (and index) UCDXML for a version.
|
|
25
|
+
#
|
|
26
|
+
# @param version [String, nil] explicit version, or omit for default
|
|
27
|
+
def download(version = nil)
|
|
28
|
+
intent = resolve_intent(version, options[:latest])
|
|
29
|
+
actual = Ucd::VersionResolver.resolve(intent)
|
|
30
|
+
|
|
31
|
+
path = Ucd::Downloader.download(actual, force: options[:force])
|
|
32
|
+
Ucd::IndexBuilder.build(actual) unless index_present?(actual)
|
|
33
|
+
puts "UCD #{actual} ready at: #{path}"
|
|
34
|
+
rescue Ucd::Error => e
|
|
35
|
+
warn "ERROR: #{e.message}"
|
|
36
|
+
exit 1
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
desc "status", "Show cached UCD versions and default version"
|
|
40
|
+
# Print a one-screen summary of the local cache state.
|
|
41
|
+
def status
|
|
42
|
+
cached = Ucd::CacheManager.cached_versions
|
|
43
|
+
puts "Default version: #{Ucd::Config.default_version}"
|
|
44
|
+
puts "Cache root: #{Ucd::CacheManager.root}"
|
|
45
|
+
puts "Cached versions: #{cached.empty? ? '(none)' : cached.join(', ')}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
desc "path [VERSION]", "Print local cache directory for a version"
|
|
49
|
+
# Print the cache directory path for a version (default: default version).
|
|
50
|
+
#
|
|
51
|
+
# @param version [String, nil]
|
|
52
|
+
def path(version = nil)
|
|
53
|
+
actual = Ucd::VersionResolver.resolve(version)
|
|
54
|
+
puts Ucd::CacheManager.version_dir(actual)
|
|
55
|
+
rescue Ucd::UnknownVersionError => e
|
|
56
|
+
warn "ERROR: #{e.message}"
|
|
57
|
+
exit 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
desc "list", "List UCD versions known to this Fontisan release"
|
|
61
|
+
# Print the curated list of versions this Fontisan release supports.
|
|
62
|
+
def list
|
|
63
|
+
Ucd::Config.known_versions.each { |v| puts v }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
desc "remove VERSION", "Remove a cached UCD version"
|
|
67
|
+
# Delete one cached version. No-op if absent.
|
|
68
|
+
#
|
|
69
|
+
# @param version [String]
|
|
70
|
+
def remove(version)
|
|
71
|
+
Ucd::VersionResolver.validate!(version)
|
|
72
|
+
unless Ucd::CacheManager.cached?(version)
|
|
73
|
+
warn "Version #{version} is not cached; nothing to remove."
|
|
74
|
+
return
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
Ucd::CacheManager.remove_version(version)
|
|
78
|
+
puts "Removed UCD #{version}."
|
|
79
|
+
rescue Ucd::UnknownVersionError => e
|
|
80
|
+
warn "ERROR: #{e.message}"
|
|
81
|
+
exit 1
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def resolve_intent(version, latest)
|
|
87
|
+
return :latest if latest && version.nil?
|
|
88
|
+
|
|
89
|
+
version
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def index_present?(version)
|
|
93
|
+
Ucd::CacheManager.blocks_index_path(version).exist? &&
|
|
94
|
+
Ucd::CacheManager.scripts_index_path(version).exist?
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
data/lib/fontisan/cli.rb
CHANGED
|
@@ -25,6 +25,93 @@ module Fontisan
|
|
|
25
25
|
desc: "Suppress non-error output",
|
|
26
26
|
aliases: "-q"
|
|
27
27
|
|
|
28
|
+
desc "ucd", "Manage local UCD cache (subcommands)", hide: true
|
|
29
|
+
subcommand "ucd", UcdCli
|
|
30
|
+
|
|
31
|
+
desc "cldr", "Manage local CLDR cache (subcommands)", hide: true
|
|
32
|
+
subcommand "cldr", CldrCli
|
|
33
|
+
|
|
34
|
+
desc "audit PATH", "Produce a per-face font audit report, or diff/summarize"
|
|
35
|
+
long_desc <<~DESC
|
|
36
|
+
Produce a complete per-face font audit report covering identity, style,
|
|
37
|
+
metrics, coverage (Unicode blocks/scripts), licensing, hinting, color
|
|
38
|
+
capabilities, variable font detail, and OpenType layout features.
|
|
39
|
+
|
|
40
|
+
For TTC/OTC/dfont collections, one report per face is produced. Use
|
|
41
|
+
--output to write reports to disk; --font-index to audit a single face.
|
|
42
|
+
|
|
43
|
+
Variants:
|
|
44
|
+
fontisan audit FONT.ttf
|
|
45
|
+
fontisan audit COLLECTION.ttc
|
|
46
|
+
fontisan audit DIR/ --recursive --summary
|
|
47
|
+
fontisan audit --compare A.ttf B.ttf
|
|
48
|
+
fontisan audit --compare A.yaml B.yaml
|
|
49
|
+
|
|
50
|
+
Output formats: text (default), yaml, json.
|
|
51
|
+
|
|
52
|
+
Use --brief for a fast inventory pass that skips metrics, hinting,
|
|
53
|
+
color, variable-font detail, OpenType layout, and UCD/CLDR
|
|
54
|
+
aggregation — only identity, style, licensing, and codepoint coverage.
|
|
55
|
+
DESC
|
|
56
|
+
option :font_index, type: :numeric,
|
|
57
|
+
desc: "Audit only this face in a collection (default: all)"
|
|
58
|
+
option :all_codepoints, type: :boolean, default: false,
|
|
59
|
+
desc: "Include the full per-codepoint list " \
|
|
60
|
+
"(defaults to compact range view)"
|
|
61
|
+
option :ucd_version, type: :string,
|
|
62
|
+
desc: "UCD version to aggregate against " \
|
|
63
|
+
"(default: configured default; 'latest' to probe)"
|
|
64
|
+
option :with_language_coverage, type: :boolean, default: false,
|
|
65
|
+
desc: "Compute coverage % per CLDR language " \
|
|
66
|
+
"(requires CLDR cache; auto-downloads)"
|
|
67
|
+
option :cldr_version, type: :string,
|
|
68
|
+
desc: "CLDR version (default: configured default; " \
|
|
69
|
+
"'latest' to probe)"
|
|
70
|
+
option :brief, type: :boolean, default: false,
|
|
71
|
+
desc: "Skip metrics/hinting/color/layout/UCD/CLDR for a " \
|
|
72
|
+
"fast inventory pass"
|
|
73
|
+
option :compare, type: :boolean, default: false,
|
|
74
|
+
desc: "Diff two fonts or two saved reports " \
|
|
75
|
+
"(requires exactly two PATHs)"
|
|
76
|
+
option :recursive, type: :boolean, default: false,
|
|
77
|
+
desc: "Audit every font under a directory tree " \
|
|
78
|
+
"(library mode)"
|
|
79
|
+
option :summary, type: :boolean, default: false,
|
|
80
|
+
desc: "Produce a LibrarySummary over a directory of fonts"
|
|
81
|
+
option :output, type: :string,
|
|
82
|
+
desc: "Output directory (collections/library) or file " \
|
|
83
|
+
"(single font / compare)",
|
|
84
|
+
aliases: "-o"
|
|
85
|
+
# Produce a complete font audit report, or diff two fonts/reports,
|
|
86
|
+
# or summarize a whole library.
|
|
87
|
+
#
|
|
88
|
+
# @param paths [Array<String>] one path (audit/library), or two paths (--compare)
|
|
89
|
+
def audit(*paths)
|
|
90
|
+
raise Thor::Error, "audit requires one PATH (or two with --compare)" if paths.empty?
|
|
91
|
+
|
|
92
|
+
if options[:compare]
|
|
93
|
+
unless paths.length == 2
|
|
94
|
+
raise Thor::Error,
|
|
95
|
+
"audit --compare requires exactly two paths"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
return run_compare(paths[0], paths[1])
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
raise Thor::Error, "audit requires exactly one PATH" unless paths.length == 1
|
|
102
|
+
|
|
103
|
+
path = paths[0]
|
|
104
|
+
if Dir.exist?(path) && !library_mode?(path)
|
|
105
|
+
raise Thor::Error,
|
|
106
|
+
"audit on a directory requires --recursive or --summary"
|
|
107
|
+
end
|
|
108
|
+
return run_library_audit(path) if library_mode?(path)
|
|
109
|
+
|
|
110
|
+
run_single_audit(path)
|
|
111
|
+
rescue Errno::ENOENT, Error, Thor::Error => e
|
|
112
|
+
handle_error(e)
|
|
113
|
+
end
|
|
114
|
+
|
|
28
115
|
desc "info PATH", "Display font information"
|
|
29
116
|
option :brief, type: :boolean, default: false,
|
|
30
117
|
desc: "Brief mode - only essential info (5x faster, uses metadata loading)",
|
|
@@ -203,7 +290,9 @@ module Fontisan
|
|
|
203
290
|
|
|
204
291
|
desc "convert FONT_FILE", "Convert font to different format"
|
|
205
292
|
option :to, type: :string, required: true,
|
|
206
|
-
desc: "Target format
|
|
293
|
+
desc: "Target format: ttf, otf, type1, t1, ttc, otc, dfont, svg, " \
|
|
294
|
+
"woff (zlib — works on all browsers incl. legacy), " \
|
|
295
|
+
"woff2 (Brotli — ~30% smaller, modern browsers only)",
|
|
207
296
|
aliases: "-t"
|
|
208
297
|
option :output, type: :string,
|
|
209
298
|
desc: "Output file path (required unless --show-options)",
|
|
@@ -255,6 +344,19 @@ module Fontisan
|
|
|
255
344
|
desc: "Enable table optimization"
|
|
256
345
|
option :decompose_on_output, type: :boolean,
|
|
257
346
|
desc: "Decompose on output (generating option)"
|
|
347
|
+
# Compression knobs — declared by each strategy (single source of truth:
|
|
348
|
+
# Converters::* strategies). Cross-format misuse is caught at convert time
|
|
349
|
+
# by FormatConverter.validate_options_for_target! with a clear ArgumentError.
|
|
350
|
+
option :zlib_level, type: :numeric,
|
|
351
|
+
desc: "WOFF only: zlib compression level (0–9, default 6)"
|
|
352
|
+
option :uncompressed, type: :boolean,
|
|
353
|
+
desc: "WOFF only: store tables uncompressed (legal per WOFF 1.0 §5.1)"
|
|
354
|
+
option :compression_threshold, type: :numeric,
|
|
355
|
+
desc: "WOFF only: skip compression for tables smaller than N bytes (default 100)"
|
|
356
|
+
option :brotli_quality, type: :numeric,
|
|
357
|
+
desc: "WOFF2 only: Brotli quality (0–11, default 11)"
|
|
358
|
+
option :transform_tables, type: :boolean,
|
|
359
|
+
desc: "WOFF2 only: apply glyf/loca and hmtx transformations"
|
|
258
360
|
# Convert a font to a different format using the universal transformation pipeline.
|
|
259
361
|
#
|
|
260
362
|
# Supported conversions:
|
|
@@ -264,6 +366,14 @@ module Fontisan
|
|
|
264
366
|
# - Variable fonts: Automatic variation preservation or instance generation
|
|
265
367
|
# - Collections (TTC/OTC/dfont): Preserve mixed TTF+OTF by default, or standardize with --target-format
|
|
266
368
|
#
|
|
369
|
+
# Web fonts — WOFF vs WOFF2:
|
|
370
|
+
# WOFF uses zlib compression and works on every browser that supports
|
|
371
|
+
# web fonts at all (IE9+, all evergreen browsers). WOFF2 uses Brotli and
|
|
372
|
+
# is ~30% smaller but requires modern browsers (Chrome 36+, Firefox 39+,
|
|
373
|
+
# Safari 12+, Edge 14+). Old browsers cannot decode Brotli, so for legacy
|
|
374
|
+
# support serve WOFF. Tune knobs with --zlib-level / --brotli-quality /
|
|
375
|
+
# --uncompressed (WOFF only) / --transform-tables (WOFF2 only).
|
|
376
|
+
#
|
|
267
377
|
# Collection Format Support:
|
|
268
378
|
# TTC, OTC, and dfont all support mixed TrueType and OpenType fonts. By default, original font formats
|
|
269
379
|
# are preserved during collection conversion (--target-format preserve). Use --target-format ttf to
|
|
@@ -678,6 +788,96 @@ module Fontisan
|
|
|
678
788
|
puts output unless options[:quiet]
|
|
679
789
|
end
|
|
680
790
|
|
|
791
|
+
# Write audit reports to disk. If `target` is a directory (or there are
|
|
792
|
+
# multiple reports), one file per face is written under it. If `target`
|
|
793
|
+
# is a file path and there's exactly one report, that exact path is used.
|
|
794
|
+
#
|
|
795
|
+
# @param reports [Array<Models::Audit::AuditReport>]
|
|
796
|
+
# @param target [String] directory (collection) or file path (single)
|
|
797
|
+
# @param format [String] "yaml" or "json"
|
|
798
|
+
# @return [void]
|
|
799
|
+
def write_audit_outputs(reports, target, format)
|
|
800
|
+
sym_format = format.to_sym
|
|
801
|
+
|
|
802
|
+
if reports.one? && !Dir.exist?(target) && File.extname(target) != ""
|
|
803
|
+
File.write(target, serialize_report(reports.first, sym_format))
|
|
804
|
+
puts "Wrote #{target}" unless options[:quiet]
|
|
805
|
+
return
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
paths = Commands::AuditCommand.write_reports(reports, to: target,
|
|
809
|
+
format: sym_format)
|
|
810
|
+
paths.each { |p| puts "Wrote #{p}" unless options[:quiet] }
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
def run_single_audit(path)
|
|
814
|
+
cmd_options = options.dup
|
|
815
|
+
cmd_options.delete(:output)
|
|
816
|
+
# Audit's --brief selects a cheap extractor subset but still needs FULL
|
|
817
|
+
# font loading (Coverage reads cmap). Translate to :audit_brief so
|
|
818
|
+
# BaseCommand's :brief → METADATA shortcut does not fire.
|
|
819
|
+
cmd_options[:audit_brief] = cmd_options.delete(:brief) if cmd_options.key?(:brief)
|
|
820
|
+
command = Commands::AuditCommand.new(path, cmd_options)
|
|
821
|
+
reports = Array(command.run)
|
|
822
|
+
|
|
823
|
+
if options[:output]
|
|
824
|
+
write_audit_outputs(reports, options[:output], options[:format])
|
|
825
|
+
else
|
|
826
|
+
reports.each { |r| output_result(r) }
|
|
827
|
+
end
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
def run_compare(left_path, right_path)
|
|
831
|
+
cmd = Commands::AuditCompareCommand.new(left_path, right_path, options)
|
|
832
|
+
diff = cmd.run
|
|
833
|
+
return if options[:quiet]
|
|
834
|
+
|
|
835
|
+
if options[:output]
|
|
836
|
+
File.write(options[:output], serialize_report(diff, options[:format].to_sym))
|
|
837
|
+
puts "Wrote #{options[:output]}"
|
|
838
|
+
return
|
|
839
|
+
end
|
|
840
|
+
|
|
841
|
+
output_result(diff)
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
def run_library_audit(path)
|
|
845
|
+
cmd = Commands::AuditLibraryCommand.new(
|
|
846
|
+
path,
|
|
847
|
+
recursive: options[:recursive],
|
|
848
|
+
options: options.dup,
|
|
849
|
+
)
|
|
850
|
+
summary = cmd.run
|
|
851
|
+
announce_skipped(cmd.skipped)
|
|
852
|
+
return if options[:quiet]
|
|
853
|
+
|
|
854
|
+
if options[:output]
|
|
855
|
+
File.write(options[:output],
|
|
856
|
+
serialize_report(summary, options[:format].to_sym))
|
|
857
|
+
puts "Wrote #{options[:output]}"
|
|
858
|
+
return
|
|
859
|
+
end
|
|
860
|
+
|
|
861
|
+
output_result(summary)
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
# Library mode triggers when the path is a directory and either
|
|
865
|
+
# --recursive or --summary is requested. A single-file audit ignores
|
|
866
|
+
# both flags (a TTC is audited face-by-face via run_single_audit).
|
|
867
|
+
def library_mode?(path)
|
|
868
|
+
Dir.exist?(path) && (options[:recursive] || options[:summary])
|
|
869
|
+
end
|
|
870
|
+
|
|
871
|
+
def announce_skipped(skipped)
|
|
872
|
+
return if skipped.empty?
|
|
873
|
+
|
|
874
|
+
skipped.each { |p| warn "skipped #{p}" }
|
|
875
|
+
end
|
|
876
|
+
|
|
877
|
+
def serialize_report(report, format)
|
|
878
|
+
format == :json ? report.to_json : report.to_yaml
|
|
879
|
+
end
|
|
880
|
+
|
|
681
881
|
# Format result as human-readable text.
|
|
682
882
|
#
|
|
683
883
|
# @param result [Object] The result object to format
|
|
@@ -720,7 +920,6 @@ module Fontisan
|
|
|
720
920
|
#
|
|
721
921
|
# @return [void]
|
|
722
922
|
def list_available_tests
|
|
723
|
-
require_relative "validators/profile_loader"
|
|
724
923
|
profiles = Validators::ProfileLoader.all_profiles
|
|
725
924
|
puts "Available validation profiles:"
|
|
726
925
|
profiles.each do |profile_name, config|
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../font_writer"
|
|
4
|
-
require_relative "../error"
|
|
5
|
-
require_relative "../validation/collection_validator"
|
|
6
|
-
|
|
7
3
|
module Fontisan
|
|
8
4
|
module Collection
|
|
9
5
|
# DfontBuilder creates Apple dfont (Data Fork Font) resource fork structures
|
|
@@ -15,8 +15,6 @@ module Fontisan
|
|
|
15
15
|
# @param fonts [Array<TrueTypeFont, OpenTypeFont>] Array of fonts
|
|
16
16
|
# @return [Models::TableSharingInfo] Sharing statistics
|
|
17
17
|
def calculate_table_sharing_for_fonts(fonts)
|
|
18
|
-
require_relative "../models/table_sharing_info"
|
|
19
|
-
|
|
20
18
|
# Build table hash map (checksum -> size)
|
|
21
19
|
table_map = {}
|
|
22
20
|
total_table_size = 0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Autoload hub for the Fontisan::Collection namespace.
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Collection
|
|
7
|
+
autoload :Builder, "fontisan/collection/builder"
|
|
8
|
+
autoload :DfontBuilder, "fontisan/collection/dfont_builder"
|
|
9
|
+
autoload :OffsetCalculator, "fontisan/collection/offset_calculator"
|
|
10
|
+
autoload :SharedLogic, "fontisan/collection/shared_logic"
|
|
11
|
+
autoload :TableAnalyzer, "fontisan/collection/table_analyzer"
|
|
12
|
+
autoload :TableDeduplicator, "fontisan/collection/table_deduplicator"
|
|
13
|
+
autoload :Writer, "fontisan/collection/writer"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Commands
|
|
7
|
+
# Produces a complete per-face font audit report.
|
|
8
|
+
#
|
|
9
|
+
# One AuditReport per face. For standalone fonts (TTF/OTF/WOFF/WOFF2),
|
|
10
|
+
# #run returns a single AuditReport. For collections (TTC/OTC/dfont),
|
|
11
|
+
# #run returns an Array<AuditReport> — one per face, in source order.
|
|
12
|
+
#
|
|
13
|
+
# The report is assembled by running every extractor in
|
|
14
|
+
# {Audit::Registry} against an {Audit::Context}. Each extractor
|
|
15
|
+
# owns one concern (provenance, identity, style, coverage,
|
|
16
|
+
# aggregations, …). Adding a new concern means adding one
|
|
17
|
+
# extractor class and one line in the registry — AuditCommand
|
|
18
|
+
# itself never changes.
|
|
19
|
+
class AuditCommand < BaseCommand
|
|
20
|
+
# @return [Models::Audit::AuditReport, Array<Models::Audit::AuditReport>]
|
|
21
|
+
def run
|
|
22
|
+
if FontLoader.collection?(@font_path)
|
|
23
|
+
audit_collection
|
|
24
|
+
else
|
|
25
|
+
audit_face(@font, 0, 1)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Write one file per face under `to` (a directory). Pure utility —
|
|
30
|
+
# operates on a pre-built reports array, no font_path required.
|
|
31
|
+
#
|
|
32
|
+
# @param reports [Array<Models::Audit::AuditReport>]
|
|
33
|
+
# @param to [String] output directory; created if missing
|
|
34
|
+
# @param format [Symbol] :yaml or :json
|
|
35
|
+
# @return [Array<String>] written file paths
|
|
36
|
+
def self.write_reports(reports, to:, format: :yaml)
|
|
37
|
+
FileUtils.mkdir_p(to)
|
|
38
|
+
|
|
39
|
+
reports.map do |report|
|
|
40
|
+
path = File.join(to, output_filename(report, format))
|
|
41
|
+
content = format == :json ? report.to_json : report.to_yaml
|
|
42
|
+
File.write(path, content)
|
|
43
|
+
path
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Compute the per-face filename for a report.
|
|
48
|
+
#
|
|
49
|
+
# @param report [Models::Audit::AuditReport]
|
|
50
|
+
# @param format [Symbol] :yaml or :json
|
|
51
|
+
# @return [String] filename only (no directory)
|
|
52
|
+
def self.output_filename(report, format)
|
|
53
|
+
ext = format == :json ? "json" : "yaml"
|
|
54
|
+
base = if report.num_fonts_in_source == 1
|
|
55
|
+
safe_filename(report.postscript_name || report.family_name || "font")
|
|
56
|
+
else
|
|
57
|
+
format("%<idx>02d-%<name>s",
|
|
58
|
+
idx: report.font_index,
|
|
59
|
+
name: safe_filename(report.postscript_name || "face"))
|
|
60
|
+
end
|
|
61
|
+
"#{base}.#{ext}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Sanitize an arbitrary string into a filesystem-safe basename.
|
|
65
|
+
#
|
|
66
|
+
# @param name [String, nil]
|
|
67
|
+
# @return [String]
|
|
68
|
+
def self.safe_filename(name)
|
|
69
|
+
return "font" if name.nil? || name.empty?
|
|
70
|
+
|
|
71
|
+
name.gsub(/[^A-Za-z0-9._-]/, "_")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def audit_collection
|
|
77
|
+
collection = FontLoader.load_collection(@font_path)
|
|
78
|
+
num = collection.num_fonts
|
|
79
|
+
Array.new(num) do |index|
|
|
80
|
+
font = FontLoader.load(@font_path, font_index: index,
|
|
81
|
+
mode: LoadingModes::FULL)
|
|
82
|
+
audit_face(font, index, num)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def audit_face(font, font_index, num_fonts_in_source)
|
|
87
|
+
context = Audit::Context.new(
|
|
88
|
+
font: font,
|
|
89
|
+
font_path: @font_path,
|
|
90
|
+
font_index: font_index,
|
|
91
|
+
num_fonts_in_source: num_fonts_in_source,
|
|
92
|
+
options: @options,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
fields = {}
|
|
96
|
+
Audit::Registry.each(mode: audit_mode) do |extractor_class|
|
|
97
|
+
fields.merge!(extractor_class.new.extract(context))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
fields[:warning] = combine_warnings(
|
|
101
|
+
context.ucd[:warning],
|
|
102
|
+
context.cldr&.dig(:warning),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
Models::Audit::AuditReport.new(**fields)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Audit's --brief selects a cheap extractor subset (identity, style,
|
|
109
|
+
# licensing, coverage) but still requires FULL font loading — the
|
|
110
|
+
# Coverage extractor reads `cmap`. The CLI translates the user-facing
|
|
111
|
+
# `--brief` flag into `:audit_brief` so BaseCommand's `:brief →
|
|
112
|
+
# LoadingModes::METADATA` shortcut doesn't fire.
|
|
113
|
+
def audit_mode
|
|
114
|
+
@options[:audit_brief] ? :brief : :full
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def combine_warnings(*warnings)
|
|
118
|
+
compacted = warnings.flatten.compact
|
|
119
|
+
compacted.empty? ? nil : compacted.join("; ")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Commands
|
|
5
|
+
# Diffs two faces or two saved audit reports.
|
|
6
|
+
#
|
|
7
|
+
# Each input is one of:
|
|
8
|
+
# - A path to a `.yaml`/`.json` file previously written by
|
|
9
|
+
# `fontisan audit -o`. Loaded as an AuditReport.
|
|
10
|
+
# - A path to a font file. Audited on-the-fly via AuditCommand.
|
|
11
|
+
#
|
|
12
|
+
# Returns an {Models::Audit::AuditDiff}. The CLI renders it as
|
|
13
|
+
# YAML/JSON (text formatter lands in TODO 25).
|
|
14
|
+
#
|
|
15
|
+
# Mixed inputs are allowed (font vs. saved report), which is useful
|
|
16
|
+
# for tracking a font's evolution against a checked-in baseline.
|
|
17
|
+
class AuditCompareCommand
|
|
18
|
+
# @param left_path [String] path to font file or saved report
|
|
19
|
+
# @param right_path [String] path to font file or saved report
|
|
20
|
+
# @param options [Hash] forwarded to AuditCommand for any input
|
|
21
|
+
# that needs to be audited fresh
|
|
22
|
+
def initialize(left_path, right_path, options = {})
|
|
23
|
+
@left_path = left_path
|
|
24
|
+
@right_path = right_path
|
|
25
|
+
@options = options
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [Models::Audit::AuditDiff]
|
|
29
|
+
def run
|
|
30
|
+
left_report = load_report(@left_path)
|
|
31
|
+
right_report = load_report(@right_path)
|
|
32
|
+
Audit::Differ.new(left_report, right_report).diff
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def load_report(path)
|
|
38
|
+
if saved_report?(path)
|
|
39
|
+
load_saved_report(path)
|
|
40
|
+
else
|
|
41
|
+
AuditCommand.new(path, audit_options).run
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def saved_report?(path)
|
|
46
|
+
ext = File.extname(path).downcase
|
|
47
|
+
[".yaml", ".yml", ".json"].include?(ext)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def load_saved_report(path)
|
|
51
|
+
case File.extname(path).downcase
|
|
52
|
+
when ".json"
|
|
53
|
+
Models::Audit::AuditReport.from_json(File.read(path))
|
|
54
|
+
else
|
|
55
|
+
Models::Audit::AuditReport.from_yaml(File.read(path))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Forward only the audit-relevant options when auditing fresh fonts.
|
|
60
|
+
# Drops `--compare` (consumed here) and `--output` (no file output).
|
|
61
|
+
def audit_options
|
|
62
|
+
@options.except(:compare, :output)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Commands
|
|
5
|
+
# Audits every font in a directory (tree) and rolls the per-face
|
|
6
|
+
# reports up into a {Models::Audit::LibrarySummary}.
|
|
7
|
+
#
|
|
8
|
+
# Thin wrapper over {Audit::LibraryAuditor}: validates the root
|
|
9
|
+
# path exists, delegates to the auditor, returns the summary.
|
|
10
|
+
# The auditor itself owns file discovery and per-face auditing;
|
|
11
|
+
# this command is the CLI-facing boundary that maps user-facing
|
|
12
|
+
# options onto auditor inputs.
|
|
13
|
+
class AuditLibraryCommand
|
|
14
|
+
# @param root_path [String] directory containing fonts
|
|
15
|
+
# @param recursive [Boolean] walk into subdirectories
|
|
16
|
+
# @param options [Hash] forwarded to AuditCommand for each face
|
|
17
|
+
def initialize(root_path, recursive:, options:)
|
|
18
|
+
@root_path = root_path
|
|
19
|
+
@recursive = recursive
|
|
20
|
+
@options = options
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [Models::Audit::LibrarySummary]
|
|
24
|
+
def run
|
|
25
|
+
raise Error, "library audit requires an existing directory: #{@root_path}" unless Dir.exist?(@root_path)
|
|
26
|
+
|
|
27
|
+
auditor.audit
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @return [Array<String>] files skipped during the audit pass
|
|
31
|
+
def skipped
|
|
32
|
+
auditor.skipped
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def auditor
|
|
38
|
+
@auditor ||= Audit::LibraryAuditor.new(
|
|
39
|
+
@root_path,
|
|
40
|
+
recursive: @recursive,
|
|
41
|
+
options: @options,
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|