fontisan 0.2.17 → 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/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/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 +19 -0
- 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 +14 -19
- 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 +114 -7
- data/fontisan.gemspec +0 -48
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Autoload hub for the Fontisan::Audit::Extractors namespace.
|
|
4
|
+
#
|
|
5
|
+
# Each extractor is a small MECE class with a single `#extract(context)`
|
|
6
|
+
# method returning a hash of AuditReport fields. The Audit::Registry
|
|
7
|
+
# declares the ordered list.
|
|
8
|
+
|
|
9
|
+
module Fontisan
|
|
10
|
+
module Audit
|
|
11
|
+
module Extractors
|
|
12
|
+
autoload :Base, "fontisan/audit/extractors/base"
|
|
13
|
+
autoload :Provenance, "fontisan/audit/extractors/provenance"
|
|
14
|
+
autoload :Identity, "fontisan/audit/extractors/identity"
|
|
15
|
+
autoload :Style, "fontisan/audit/extractors/style"
|
|
16
|
+
autoload :Licensing, "fontisan/audit/extractors/licensing"
|
|
17
|
+
autoload :Metrics, "fontisan/audit/extractors/metrics"
|
|
18
|
+
autoload :Hinting, "fontisan/audit/extractors/hinting"
|
|
19
|
+
autoload :ColorCapabilities, "fontisan/audit/extractors/color_capabilities"
|
|
20
|
+
autoload :VariationDetail, "fontisan/audit/extractors/variation_detail"
|
|
21
|
+
autoload :OpenTypeLayout, "fontisan/audit/extractors/opentype_layout"
|
|
22
|
+
autoload :Coverage, "fontisan/audit/extractors/coverage"
|
|
23
|
+
autoload :Aggregations, "fontisan/audit/extractors/aggregations"
|
|
24
|
+
autoload :LanguageCoverage, "fontisan/audit/extractors/language_coverage"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Audit
|
|
5
|
+
# Pure cross-face aggregation over a list of AuditReports.
|
|
6
|
+
#
|
|
7
|
+
# No I/O, no font parsing — operates only on already-built reports.
|
|
8
|
+
# Easy to spec with synthetic reports and trivially testable. The
|
|
9
|
+
# orchestrator ({LibraryAuditor}) handles file discovery and per-face
|
|
10
|
+
# auditing; this class owns the rollups that span faces.
|
|
11
|
+
#
|
|
12
|
+
# Aggregates:
|
|
13
|
+
# - aggregate_metrics: sum of total_codepoints and total_glyphs.
|
|
14
|
+
# - script_coverage: one ScriptCoverageRow per Unicode script,
|
|
15
|
+
# listing faces that cover it.
|
|
16
|
+
# - duplicate_groups: files bucketed by source_sha256 (size > 1).
|
|
17
|
+
# - license_distribution: face counts keyed by license_url.
|
|
18
|
+
class LibraryAggregator
|
|
19
|
+
# @param reports [Array<Models::Audit::AuditReport>]
|
|
20
|
+
# @return [Hash{Symbol => Object}] keys: :aggregate_metrics,
|
|
21
|
+
# :script_coverage, :duplicate_groups, :license_distribution
|
|
22
|
+
def aggregate(reports)
|
|
23
|
+
{
|
|
24
|
+
aggregate_metrics: aggregate_metrics(reports),
|
|
25
|
+
script_coverage: build_script_coverage(reports),
|
|
26
|
+
duplicate_groups: find_duplicates(reports),
|
|
27
|
+
license_distribution: license_distribution(reports),
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def aggregate_metrics(reports)
|
|
34
|
+
{
|
|
35
|
+
total_codepoints: reports.sum(&:total_codepoints),
|
|
36
|
+
total_glyphs: reports.sum(&:total_glyphs),
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def build_script_coverage(reports)
|
|
41
|
+
by_script = Hash.new { |h, k| h[k] = [] }
|
|
42
|
+
reports.each do |report|
|
|
43
|
+
face = report.postscript_name || report.source_file
|
|
44
|
+
scripts_for(report).each { |script| by_script[script] << face }
|
|
45
|
+
end
|
|
46
|
+
by_script.map do |script, faces|
|
|
47
|
+
Models::Audit::ScriptCoverageRow.new(
|
|
48
|
+
script: script,
|
|
49
|
+
face_count: faces.size,
|
|
50
|
+
faces: faces.uniq.sort,
|
|
51
|
+
)
|
|
52
|
+
end.sort_by { |row| [-row.face_count, row.script] }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def find_duplicates(reports)
|
|
56
|
+
reports.group_by(&:source_sha256)
|
|
57
|
+
.select { |_sha, group| group.size > 1 }
|
|
58
|
+
.map do |sha, group|
|
|
59
|
+
Models::Audit::DuplicateGroup.new(
|
|
60
|
+
source_sha256: sha,
|
|
61
|
+
files: group.map(&:source_file).sort,
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
.sort_by(&:source_sha256)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def license_distribution(reports)
|
|
68
|
+
reports.each_with_object({}) do |report, counts|
|
|
69
|
+
url = license_url_for(report)
|
|
70
|
+
counts[url] = counts.fetch(url, 0) + 1
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def scripts_for(report)
|
|
75
|
+
Array(report.unicode_scripts)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def license_url_for(report)
|
|
79
|
+
report.licensing&.license_url || "(none)"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Audit
|
|
7
|
+
# Orchestrates a library-wide audit pass.
|
|
8
|
+
#
|
|
9
|
+
# Owns the file-system side: discovers font files under a root path
|
|
10
|
+
# (recursively or not), audits each via {Commands::AuditCommand},
|
|
11
|
+
# and assembles a {Models::Audit::LibrarySummary} combining the
|
|
12
|
+
# per-face reports with cross-face rollups from {LibraryAggregator}.
|
|
13
|
+
#
|
|
14
|
+
# Aggregation logic lives in the pure {LibraryAggregator}; this
|
|
15
|
+
# class stays focused on discovery + per-face auditing + summary
|
|
16
|
+
# assembly. Errors auditing a single file are logged and skipped so
|
|
17
|
+
# a corrupt file doesn't abort the whole pass.
|
|
18
|
+
class LibraryAuditor
|
|
19
|
+
FONT_EXTENSIONS = %w[.ttf .otf .ttc .otc .dfont .woff .woff2
|
|
20
|
+
.pfb .pfa .svg].freeze
|
|
21
|
+
|
|
22
|
+
# @param root_path [String, Pathname] directory containing fonts
|
|
23
|
+
# @param recursive [Boolean] walk into subdirectories
|
|
24
|
+
# @param options [Hash] forwarded to AuditCommand (minus library-only keys)
|
|
25
|
+
def initialize(root_path, recursive:, options:)
|
|
26
|
+
@root_path = Pathname.new(root_path)
|
|
27
|
+
@recursive = recursive
|
|
28
|
+
@options = options
|
|
29
|
+
@aggregator = LibraryAggregator.new
|
|
30
|
+
@skipped = []
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Models::Audit::LibrarySummary]
|
|
34
|
+
def audit
|
|
35
|
+
paths = discover_font_paths
|
|
36
|
+
reports = paths.flat_map { |p| audit_one(p) }
|
|
37
|
+
rolled_up = aggregates(reports)
|
|
38
|
+
|
|
39
|
+
Models::Audit::LibrarySummary.new(
|
|
40
|
+
root_path: @root_path.to_s,
|
|
41
|
+
total_files: paths.size,
|
|
42
|
+
total_faces: reports.size,
|
|
43
|
+
scanned_extensions: scanned_extensions(paths),
|
|
44
|
+
aggregate_metrics: rolled_up[:aggregate_metrics].merge(
|
|
45
|
+
total_size_bytes: paths.sum { |p| File.size(p) },
|
|
46
|
+
),
|
|
47
|
+
script_coverage: rolled_up[:script_coverage],
|
|
48
|
+
duplicate_groups: rolled_up[:duplicate_groups],
|
|
49
|
+
license_distribution: rolled_up[:license_distribution],
|
|
50
|
+
per_face_reports: reports,
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [Array<String>] source files that could not be audited
|
|
55
|
+
attr_reader :skipped
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def discover_font_paths
|
|
60
|
+
method = @recursive ? :find : :children
|
|
61
|
+
@root_path.public_send(method).select do |entry|
|
|
62
|
+
next false unless entry.file?
|
|
63
|
+
next false if entry.symlink?
|
|
64
|
+
|
|
65
|
+
FONT_EXTENSIONS.include?(entry.extname.downcase)
|
|
66
|
+
end.map(&:to_s).sort
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def audit_one(path)
|
|
70
|
+
Array(Commands::AuditCommand.new(path, audit_options).run)
|
|
71
|
+
rescue StandardError => e
|
|
72
|
+
@skipped << "#{path}: #{e.message}"
|
|
73
|
+
[]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Drop library-only options before forwarding to AuditCommand.
|
|
77
|
+
def audit_options
|
|
78
|
+
@options.except(:recursive, :summary, :output)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def scanned_extensions(paths)
|
|
82
|
+
paths.map { |p| File.extname(p).downcase }.uniq.sort
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def aggregates(reports)
|
|
86
|
+
@aggregator.aggregate(reports)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Audit
|
|
5
|
+
# Ordered list of extractor classes run for every audit face.
|
|
6
|
+
#
|
|
7
|
+
# Order matters only for human-readable output (text formatter).
|
|
8
|
+
# All extractors are independent; their outputs are merged into
|
|
9
|
+
# one big hash before constructing the AuditReport.
|
|
10
|
+
#
|
|
11
|
+
# Add new extractors here. AuditCommand never enumerates them
|
|
12
|
+
# directly (OCP: adding a concern = one line here + one file).
|
|
13
|
+
module Registry
|
|
14
|
+
# Full audit: every concern.
|
|
15
|
+
ORDERED_EXTRACTORS = [
|
|
16
|
+
Extractors::Provenance,
|
|
17
|
+
Extractors::Identity,
|
|
18
|
+
Extractors::Style,
|
|
19
|
+
Extractors::Licensing,
|
|
20
|
+
Extractors::Metrics,
|
|
21
|
+
Extractors::Hinting,
|
|
22
|
+
Extractors::ColorCapabilities,
|
|
23
|
+
Extractors::VariationDetail,
|
|
24
|
+
Extractors::OpenTypeLayout,
|
|
25
|
+
Extractors::Coverage,
|
|
26
|
+
Extractors::Aggregations,
|
|
27
|
+
Extractors::LanguageCoverage,
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
30
|
+
# Brief audit: only the cheap, name-table-only extractors. Skips
|
|
31
|
+
# metrics/hinting/color/variation/layout (extra table loads) and
|
|
32
|
+
# aggregations/language coverage (need UCD/CLDR indices). Used by
|
|
33
|
+
# `fontisan audit --brief` for a fast inventory pass.
|
|
34
|
+
BRIEF_EXTRACTORS = [
|
|
35
|
+
Extractors::Provenance,
|
|
36
|
+
Extractors::Identity,
|
|
37
|
+
Extractors::Style,
|
|
38
|
+
Extractors::Licensing,
|
|
39
|
+
Extractors::Coverage,
|
|
40
|
+
].freeze
|
|
41
|
+
|
|
42
|
+
# Iterate the extractors appropriate for the given mode.
|
|
43
|
+
#
|
|
44
|
+
# @param mode [Symbol] :full (default) or :brief
|
|
45
|
+
# @yieldparam extractor_class [Class]
|
|
46
|
+
def self.each(mode: :full, &)
|
|
47
|
+
extractors_for(mode).each(&)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @param mode [Symbol] :full or :brief
|
|
51
|
+
# @return [Array<Class>] the extractor list for the given mode
|
|
52
|
+
def self.extractors_for(mode)
|
|
53
|
+
case mode
|
|
54
|
+
when :brief then BRIEF_EXTRACTORS
|
|
55
|
+
else ORDERED_EXTRACTORS
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Audit
|
|
5
|
+
# Extracts style descriptors from a loaded font's OS/2 and head tables.
|
|
6
|
+
# One extractor per font; cheap to construct.
|
|
7
|
+
#
|
|
8
|
+
# All fields return nil when the underlying table is absent or the
|
|
9
|
+
# value is unset (e.g., Type 1 fonts have no OS/2). Callers must
|
|
10
|
+
# tolerate nils.
|
|
11
|
+
#
|
|
12
|
+
# Scope: OS/2 + head only. fvar-derived fields (axes, named instances,
|
|
13
|
+
# variable presence) live on {Extractors::VariationDetail} — this is
|
|
14
|
+
# the MECE split between static style metadata and variation metadata.
|
|
15
|
+
#
|
|
16
|
+
# Duck typing: uses only `font.has_table?(tag)` and `font.table(tag)`.
|
|
17
|
+
# No class-specific branching — any object that honors the SFNT
|
|
18
|
+
# contract works (TrueTypeFont, OpenTypeFont, WoffFont, Woff2Font,
|
|
19
|
+
# and individual faces from collections).
|
|
20
|
+
class StyleExtractor
|
|
21
|
+
FS_SELECTION_ITALIC_BIT = 0
|
|
22
|
+
MAC_STYLE_BOLD_BIT = 0
|
|
23
|
+
private_constant :FS_SELECTION_ITALIC_BIT, :MAC_STYLE_BOLD_BIT
|
|
24
|
+
|
|
25
|
+
# @param font [Object] an SFNT-compatible font object
|
|
26
|
+
def initialize(font)
|
|
27
|
+
@font = font
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def weight_class
|
|
31
|
+
os2&.us_weight_class&.to_i
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def width_class
|
|
35
|
+
os2&.us_width_class&.to_i
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# OS/2.fsSelection bit 0 (ITALIC).
|
|
39
|
+
def italic
|
|
40
|
+
return nil unless os2
|
|
41
|
+
|
|
42
|
+
(os2.fs_selection.to_i & (1 << FS_SELECTION_ITALIC_BIT)).nonzero?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# head.macStyle bit 0 (BOLD). Per OpenType convention, bold is read
|
|
46
|
+
# from head, not OS/2.
|
|
47
|
+
def bold
|
|
48
|
+
return nil unless head
|
|
49
|
+
|
|
50
|
+
(head.mac_style.to_i & (1 << MAC_STYLE_BOLD_BIT)).nonzero?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# OS/2.panose as a space-joined 10-digit string, e.g. "2 0 5 3 0 0 0 0 0 0".
|
|
54
|
+
# Returns nil if there is no OS/2 table.
|
|
55
|
+
def panose
|
|
56
|
+
bytes = os2&.panose
|
|
57
|
+
return nil if bytes.nil?
|
|
58
|
+
|
|
59
|
+
bytes = bytes.to_a
|
|
60
|
+
return nil if bytes.empty?
|
|
61
|
+
|
|
62
|
+
bytes.join(" ")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def os2
|
|
68
|
+
return @os2 if defined?(@os2)
|
|
69
|
+
|
|
70
|
+
@os2 = @font.has_table?("OS/2") ? @font.table("OS/2") : nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def head
|
|
74
|
+
return @head if defined?(@head)
|
|
75
|
+
|
|
76
|
+
@head = @font.has_table?("head") ? @font.table("head") : nil
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Autoload hub for the Fontisan::Audit namespace.
|
|
4
|
+
#
|
|
5
|
+
# AuditCommand (under Commands::AuditCommand) builds a Context and
|
|
6
|
+
# runs every extractor in Audit::Registry, merging their outputs
|
|
7
|
+
# into a single AuditReport.
|
|
8
|
+
|
|
9
|
+
module Fontisan
|
|
10
|
+
module Audit
|
|
11
|
+
autoload :Context, "fontisan/audit/context"
|
|
12
|
+
autoload :CodepointRangeCoalescer, "fontisan/audit/codepoint_range_coalescer"
|
|
13
|
+
autoload :Differ, "fontisan/audit/differ"
|
|
14
|
+
autoload :LibraryAggregator, "fontisan/audit/library_aggregator"
|
|
15
|
+
autoload :LibraryAuditor, "fontisan/audit/library_auditor"
|
|
16
|
+
autoload :Registry, "fontisan/audit/registry"
|
|
17
|
+
autoload :Extractors, "fontisan/audit/extractors"
|
|
18
|
+
autoload :StyleExtractor, "fontisan/audit/style_extractor"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "bindata"
|
|
4
|
-
require_relative "constants"
|
|
5
|
-
require_relative "collection/shared_logic"
|
|
6
4
|
|
|
7
5
|
module Fontisan
|
|
8
6
|
# Abstract base class for font collections (TTC/OTC)
|
|
@@ -118,6 +116,29 @@ module Fontisan
|
|
|
118
116
|
num_fonts
|
|
119
117
|
end
|
|
120
118
|
|
|
119
|
+
# Whether this object represents a font collection rather than a single
|
|
120
|
+
# font. Each font class is the authority on this question.
|
|
121
|
+
#
|
|
122
|
+
# @return [Boolean]
|
|
123
|
+
def collection? = true
|
|
124
|
+
|
|
125
|
+
# Variation profile. Collections are containers; per-font variation is
|
|
126
|
+
# not exposed at the collection level.
|
|
127
|
+
#
|
|
128
|
+
# @return [Symbol] :static
|
|
129
|
+
def variation_type = :static
|
|
130
|
+
|
|
131
|
+
# Outline representation. Collections may contain mixed-flavor fonts;
|
|
132
|
+
# per-font outline type requires loading an individual font.
|
|
133
|
+
#
|
|
134
|
+
# @return [Symbol] :unknown
|
|
135
|
+
def outline_type = :unknown
|
|
136
|
+
|
|
137
|
+
# Collections have no single SFNT table directory.
|
|
138
|
+
#
|
|
139
|
+
# @return [Array<String>] empty
|
|
140
|
+
def table_names = []
|
|
141
|
+
|
|
121
142
|
# Validate format correctness
|
|
122
143
|
#
|
|
123
144
|
# @return [Boolean] true if the format is valid, false otherwise
|
|
@@ -156,10 +177,6 @@ module Fontisan
|
|
|
156
177
|
# list.fonts.each { |f| puts "#{f.index}: #{f.family_name}" }
|
|
157
178
|
# end
|
|
158
179
|
def list_fonts(io)
|
|
159
|
-
require_relative "models/collection_list_info"
|
|
160
|
-
require_relative "models/collection_font_summary"
|
|
161
|
-
require_relative "tables/name"
|
|
162
|
-
|
|
163
180
|
font_class = self.class.font_class
|
|
164
181
|
|
|
165
182
|
fonts = font_offsets.map.with_index do |offset, index|
|
|
@@ -222,9 +239,6 @@ module Fontisan
|
|
|
222
239
|
# puts "Version: #{info.version_string}"
|
|
223
240
|
# end
|
|
224
241
|
def collection_info(io, path)
|
|
225
|
-
require_relative "models/collection_info"
|
|
226
|
-
require_relative "models/table_sharing_info"
|
|
227
|
-
|
|
228
242
|
# Calculate table sharing statistics
|
|
229
243
|
table_sharing = calculate_table_sharing(io)
|
|
230
244
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Autoload hub for the Fontisan::Binary namespace.
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Binary
|
|
7
|
+
autoload :BaseRecord, "fontisan/binary/base_record"
|
|
8
|
+
autoload :OffsetTable, "fontisan/binary/structures"
|
|
9
|
+
autoload :TableDirectoryEntry, "fontisan/binary/structures"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Cldr
|
|
5
|
+
# Produces audit-ready per-language coverage from a codepoint list
|
|
6
|
+
# and a Cldr::Index of per-language exemplar sets.
|
|
7
|
+
#
|
|
8
|
+
# Pure: no I/O, no side effects.
|
|
9
|
+
module Aggregator
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# @param codepoints [Enumerable<Integer>] font's codepoints
|
|
13
|
+
# @param languages_index [Cldr::Index]
|
|
14
|
+
# @return [Array<Models::Cldr::LanguageCoverage>] sorted by
|
|
15
|
+
# descending coverage_ratio, then by language name
|
|
16
|
+
def aggregate(codepoints, languages_index)
|
|
17
|
+
font_set = Set.new(codepoints)
|
|
18
|
+
|
|
19
|
+
languages_index.entries.map do |lang, required_set|
|
|
20
|
+
covered = (font_set & required_set).size
|
|
21
|
+
total = required_set.size
|
|
22
|
+
Models::Cldr::LanguageCoverage.new(
|
|
23
|
+
language: lang,
|
|
24
|
+
covered: covered,
|
|
25
|
+
total: total,
|
|
26
|
+
coverage_ratio: total.zero? ? 0.0 : covered.fdiv(total).round(4),
|
|
27
|
+
fully_supported: total.positive? && covered == total,
|
|
28
|
+
)
|
|
29
|
+
end.sort_by { |lc| [lc.coverage_ratio * -1, lc.language] }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Cldr
|
|
7
|
+
# Manages the on-disk CLDR cache layout.
|
|
8
|
+
#
|
|
9
|
+
# Cache root resolution honors `XDG_CONFIG_HOME` per the XDG Base
|
|
10
|
+
# Directory Specification. Falls back to `~/.config` on Unix.
|
|
11
|
+
#
|
|
12
|
+
# Layout:
|
|
13
|
+
#
|
|
14
|
+
# <root>/
|
|
15
|
+
# <version>/
|
|
16
|
+
# json/ # extracted CLDR JSON archive
|
|
17
|
+
# cldr-json/
|
|
18
|
+
# cldr-characters-full/
|
|
19
|
+
# main/<lang>/characters.json
|
|
20
|
+
# index/
|
|
21
|
+
# languages.yml # built index of per-language codepoint sets
|
|
22
|
+
#
|
|
23
|
+
# No network access — all methods are pure filesystem operations.
|
|
24
|
+
module CacheManager
|
|
25
|
+
LANGUAGES_INDEX_FILENAME = "languages.yml"
|
|
26
|
+
private_constant :LANGUAGES_INDEX_FILENAME
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
# Root path of the CLDR cache.
|
|
30
|
+
# @return [Pathname]
|
|
31
|
+
def root
|
|
32
|
+
base = xdg_config_home || File.join(Dir.home, ".config")
|
|
33
|
+
Pathname.new(base).join("fontisan", "cldr")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Per-version directory.
|
|
37
|
+
# @param version [String] e.g. "46.0.0"
|
|
38
|
+
# @return [Pathname]
|
|
39
|
+
def version_dir(version)
|
|
40
|
+
root.join(version)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Directory where the raw CLDR JSON archive is extracted.
|
|
44
|
+
# @param version [String]
|
|
45
|
+
# @return [Pathname]
|
|
46
|
+
def json_dir(version)
|
|
47
|
+
version_dir(version).join("json")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Directory containing the per-language characters.json files
|
|
51
|
+
# inside the extracted archive.
|
|
52
|
+
# @param version [String]
|
|
53
|
+
# @return [Pathname]
|
|
54
|
+
def characters_main_dir(version)
|
|
55
|
+
json_dir(version).join("cldr-json", "cldr-characters-full", "main")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Directory holding the derived language index for a version.
|
|
59
|
+
# @param version [String]
|
|
60
|
+
# @return [Pathname]
|
|
61
|
+
def index_dir(version)
|
|
62
|
+
version_dir(version).join("index")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def languages_index_path(version)
|
|
66
|
+
index_dir(version).join(LANGUAGES_INDEX_FILENAME)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# True if the extracted JSON archive is present for this version.
|
|
70
|
+
# @param version [String]
|
|
71
|
+
# @return [Boolean]
|
|
72
|
+
def cached?(version)
|
|
73
|
+
characters_main_dir(version).exist?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# All versions currently in the cache (sorted ascending).
|
|
77
|
+
# @return [Array<String>]
|
|
78
|
+
def cached_versions
|
|
79
|
+
return [] unless root.exist?
|
|
80
|
+
|
|
81
|
+
root.children.select(&:directory?).map { |p| p.basename.to_s }.sort
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Create the version directory and json/index subdirs.
|
|
85
|
+
# Idempotent.
|
|
86
|
+
# @param version [String]
|
|
87
|
+
def ensure_version_dir!(version)
|
|
88
|
+
json_dir(version).mkpath
|
|
89
|
+
index_dir(version).mkpath
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Remove a version from the cache. No-op if absent.
|
|
93
|
+
# @param version [String]
|
|
94
|
+
def remove_version(version)
|
|
95
|
+
dir = version_dir(version)
|
|
96
|
+
dir.rmtree if dir.exist?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def xdg_config_home
|
|
102
|
+
env = ENV["XDG_CONFIG_HOME"]
|
|
103
|
+
return nil if env.nil? || env.empty?
|
|
104
|
+
|
|
105
|
+
env
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Cldr
|
|
7
|
+
# Single source of truth for CLDR version selection.
|
|
8
|
+
#
|
|
9
|
+
# Wraps `lib/fontisan/config/cldr.yml`. Loads the YAML once at first
|
|
10
|
+
# access and memoizes. All other Cldr::* classes resolve versions,
|
|
11
|
+
# URLs, and known-version validation through this module.
|
|
12
|
+
module Config
|
|
13
|
+
CONFIG_PATH = File.expand_path("../config/cldr.yml", __dir__)
|
|
14
|
+
private_constant :CONFIG_PATH
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# The CLDR version Fontisan uses by default for auto-download and
|
|
18
|
+
# `fontisan cldr download` (no args). String like "46.0.0".
|
|
19
|
+
def default_version
|
|
20
|
+
data[:default_version]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Array of CLDR version strings this Fontisan release recognizes.
|
|
24
|
+
# Used by VersionResolver to reject unknown versions early.
|
|
25
|
+
def known_versions
|
|
26
|
+
data[:known_versions]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Base URL for fetching CLDR JSON artifacts.
|
|
30
|
+
def base_url
|
|
31
|
+
data[:base_url]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Listing URL for `--latest` probing.
|
|
35
|
+
def listing_url
|
|
36
|
+
data[:listing_url]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Full URL to the CLDR JSON full archive for a version.
|
|
40
|
+
# @param version [String] e.g. "46.0.0"
|
|
41
|
+
# @return [String]
|
|
42
|
+
def archive_url_for(version)
|
|
43
|
+
"#{base_url}/#{version}/cldr-#{version}-json-full.zip"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# True if the version appears in `known_versions`.
|
|
47
|
+
def known?(version)
|
|
48
|
+
known_versions.include?(version)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def data
|
|
54
|
+
@data ||= YAML.load_file(CONFIG_PATH).transform_keys(&:to_sym)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|