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,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Formatters
|
|
5
|
+
# Human-readable diff of two {Models::Audit::AuditReport}s.
|
|
6
|
+
#
|
|
7
|
+
# Output groups changes by kind: scalar field changes, codepoint set
|
|
8
|
+
# deltas (added/removed counts and a preview of the ranges), then
|
|
9
|
+
# structural inventory changes (scripts, features, blocks, languages).
|
|
10
|
+
# Empty sections are omitted so a no-op diff prints only the header.
|
|
11
|
+
class AuditDiffTextRenderer
|
|
12
|
+
SEPARATOR = "=" * 80
|
|
13
|
+
LIST_LIMIT = 10
|
|
14
|
+
|
|
15
|
+
# @param diff [Models::Audit::AuditDiff]
|
|
16
|
+
def initialize(diff)
|
|
17
|
+
@diff = diff
|
|
18
|
+
@lines = []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [String]
|
|
22
|
+
def render
|
|
23
|
+
render_header
|
|
24
|
+
render_field_changes
|
|
25
|
+
render_codepoint_delta
|
|
26
|
+
render_structural_changes
|
|
27
|
+
render_empty_note
|
|
28
|
+
@lines.join("\n")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def render_header
|
|
34
|
+
@lines << "AUDIT DIFF"
|
|
35
|
+
@lines << SEPARATOR
|
|
36
|
+
@lines << "left: #{@diff.left_source}"
|
|
37
|
+
@lines << "right: #{@diff.right_source}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def render_field_changes
|
|
41
|
+
changes = Array(@diff.field_changes)
|
|
42
|
+
return if changes.empty?
|
|
43
|
+
|
|
44
|
+
section("FIELD CHANGES (#{changes.size})")
|
|
45
|
+
changes.each do |change|
|
|
46
|
+
@lines << " #{change.field}: #{change.left.inspect} → #{change.right.inspect}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def render_codepoint_delta
|
|
51
|
+
delta = @diff.codepoints
|
|
52
|
+
return unless delta && (delta.added_count.to_i.positive? || delta.removed_count.to_i.positive?)
|
|
53
|
+
|
|
54
|
+
section("CODEPOINT COVERAGE")
|
|
55
|
+
@lines << " added: #{delta.added_count}"
|
|
56
|
+
@lines << " removed: #{delta.removed_count}"
|
|
57
|
+
@lines << " unchanged: #{delta.unchanged_count}"
|
|
58
|
+
preview_added(delta)
|
|
59
|
+
preview_removed(delta)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def preview_added(delta)
|
|
63
|
+
ranges = Array(delta.added)
|
|
64
|
+
return if ranges.empty?
|
|
65
|
+
|
|
66
|
+
@lines << " + #{format_ranges(ranges)}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def preview_removed(delta)
|
|
70
|
+
ranges = Array(delta.removed)
|
|
71
|
+
return if ranges.empty?
|
|
72
|
+
|
|
73
|
+
@lines << " - #{format_ranges(ranges)}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def render_structural_changes
|
|
77
|
+
render_set("SCRIPTS", @diff.added_scripts, @diff.removed_scripts)
|
|
78
|
+
render_set("FEATURES", @diff.added_features, @diff.removed_features)
|
|
79
|
+
render_set("BLOCKS", @diff.added_blocks, @diff.removed_blocks)
|
|
80
|
+
render_set("LANGUAGES", @diff.added_languages, @diff.removed_languages)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def render_set(name, added, removed)
|
|
84
|
+
added = Array(added)
|
|
85
|
+
removed = Array(removed)
|
|
86
|
+
return if added.empty? && removed.empty?
|
|
87
|
+
|
|
88
|
+
section("#{name} CHANGES")
|
|
89
|
+
@lines << " + #{truncate(added)}" unless added.empty?
|
|
90
|
+
@lines << " - #{truncate(removed)}" unless removed.empty?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def render_empty_note
|
|
94
|
+
return unless @diff.empty?
|
|
95
|
+
|
|
96
|
+
@lines << ""
|
|
97
|
+
@lines << "(no differences)"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# ---- helpers --------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
def section(title)
|
|
103
|
+
@lines << ""
|
|
104
|
+
@lines << title
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def truncate(list)
|
|
108
|
+
shown = list.first(LIST_LIMIT).join(", ")
|
|
109
|
+
shown += ", ..." if list.size > LIST_LIMIT
|
|
110
|
+
shown
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def format_ranges(ranges)
|
|
114
|
+
shown = ranges.first(LIST_LIMIT).map do |r|
|
|
115
|
+
"U+#{format('%04X', r.first_cp)}-U+#{format('%04X', r.last_cp)}"
|
|
116
|
+
end.join(", ")
|
|
117
|
+
shown += ", ..." if ranges.size > LIST_LIMIT
|
|
118
|
+
shown
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Formatters
|
|
5
|
+
# Human-readable, sectioned view of an {Models::Audit::AuditReport}.
|
|
6
|
+
#
|
|
7
|
+
# The text formatter is the default `--format text` output for
|
|
8
|
+
# `fontisan audit`. Complements YAML/JSON (machine-facing) with a
|
|
9
|
+
# terse, scannable terminal view. Every section is nil-safe so the
|
|
10
|
+
# same renderer covers full OpenType/TrueType faces, Type 1 fonts
|
|
11
|
+
# (no OS/2, no metrics, no layout), and partial reports.
|
|
12
|
+
class AuditTextRenderer
|
|
13
|
+
SEPARATOR = "=" * 80
|
|
14
|
+
LABEL_WIDTH = 18
|
|
15
|
+
LIST_LIMIT = 10
|
|
16
|
+
|
|
17
|
+
WIDTH_NAMES = {
|
|
18
|
+
1 => "Ultra-condensed", 2 => "Extra-condensed", 3 => "Condensed",
|
|
19
|
+
4 => "Semi-condensed", 5 => "Medium", 6 => "Semi-expanded",
|
|
20
|
+
7 => "Expanded", 8 => "Extra-expanded", 9 => "Ultra-expanded"
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
# @param report [Models::Audit::AuditReport]
|
|
24
|
+
def initialize(report)
|
|
25
|
+
@report = report
|
|
26
|
+
@lines = []
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [String]
|
|
30
|
+
def render
|
|
31
|
+
render_header
|
|
32
|
+
render_identity
|
|
33
|
+
render_style
|
|
34
|
+
render_metrics
|
|
35
|
+
render_coverage
|
|
36
|
+
render_blocks
|
|
37
|
+
render_licensing
|
|
38
|
+
render_hinting
|
|
39
|
+
render_color
|
|
40
|
+
render_variation
|
|
41
|
+
render_opentype_layout
|
|
42
|
+
render_language_coverage
|
|
43
|
+
render_warnings
|
|
44
|
+
@lines.join("\n")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def render_header
|
|
50
|
+
@lines << (@report.postscript_name || @report.family_name || "(unknown)")
|
|
51
|
+
@lines << SEPARATOR
|
|
52
|
+
@lines << two_col("generated_at:", @report.generated_at,
|
|
53
|
+
"fontisan:", @report.fontisan_version)
|
|
54
|
+
@lines << "source_sha256: #{@report.source_sha256}"
|
|
55
|
+
@lines << "source_file: #{@report.source_file}"
|
|
56
|
+
@lines << two_col("source_format:", @report.source_format,
|
|
57
|
+
"layout:", layout_descriptor)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def layout_descriptor
|
|
61
|
+
if @report.num_fonts_in_source.nil? || @report.num_fonts_in_source <= 1
|
|
62
|
+
"single face (1/1)"
|
|
63
|
+
else
|
|
64
|
+
format("collection face (%<idx>d/%<total>d)",
|
|
65
|
+
idx: (@report.font_index || 0) + 1,
|
|
66
|
+
total: @report.num_fonts_in_source)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def render_identity
|
|
71
|
+
section("IDENTITY")
|
|
72
|
+
row("Family", @report.family_name)
|
|
73
|
+
row("Subfamily", @report.subfamily_name)
|
|
74
|
+
row("Full name", @report.full_name)
|
|
75
|
+
row("PostScript", @report.postscript_name)
|
|
76
|
+
row("Version", @report.version)
|
|
77
|
+
row("Revision", @report.font_revision)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def render_style
|
|
81
|
+
section("STYLE")
|
|
82
|
+
row("Weight class", weight_descriptor)
|
|
83
|
+
row("Width class", width_descriptor)
|
|
84
|
+
row("Bold", yes_no(@report.bold))
|
|
85
|
+
row("Italic", yes_no(@report.italic))
|
|
86
|
+
row("PANOSE", @report.panose)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def render_metrics
|
|
90
|
+
return unless @report.metrics
|
|
91
|
+
|
|
92
|
+
m = @report.metrics
|
|
93
|
+
section("METRICS")
|
|
94
|
+
row("unitsPerEm", m.units_per_em)
|
|
95
|
+
row("hhea", "ascent: #{m.hhea_ascent} / descent: #{m.hhea_descent} / line gap: #{m.hhea_line_gap}") if m.hhea_ascent
|
|
96
|
+
row("OS/2 typo", "ascent: #{m.typo_ascender} / descent: #{m.typo_descender} / line gap: #{m.typo_line_gap}") if m.typo_ascender
|
|
97
|
+
row("OS/2 win", "ascent: #{m.win_ascent} / descent: #{m.win_descent}") if m.win_ascent
|
|
98
|
+
row("x-height", m.x_height)
|
|
99
|
+
row("cap height", m.cap_height)
|
|
100
|
+
row("bbox", bbox_descriptor(m)) if m.bbox_x_min || m.bbox_x_max
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def render_coverage
|
|
104
|
+
section("COVERAGE")
|
|
105
|
+
row("Codepoints", @report.total_codepoints)
|
|
106
|
+
row("Glyphs", @report.total_glyphs)
|
|
107
|
+
row("cmap subtables", format("%s", Array(@report.cmap_subtables).join(", "))) unless Array(@report.cmap_subtables).empty?
|
|
108
|
+
row("Ranges (top #{LIST_LIMIT})", codepoint_range_preview)
|
|
109
|
+
row("Unicode scripts", truncate_list(@report.unicode_scripts))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def render_blocks
|
|
113
|
+
blocks = Array(@report.blocks)
|
|
114
|
+
return if blocks.empty?
|
|
115
|
+
|
|
116
|
+
section("UNICODE BLOCKS (top #{LIST_LIMIT} by fill ratio)")
|
|
117
|
+
blocks.sort_by { |b| -(b.fill_ratio || 0) }.first(LIST_LIMIT).each do |block|
|
|
118
|
+
ratio = block.fill_ratio ? format("%<r>d%%", r: (block.fill_ratio * 100).round) : "?"
|
|
119
|
+
@lines << format(" %<name>-40s %<covered>d/%<total>d (%<ratio>s)",
|
|
120
|
+
name: "#{block.name}:", covered: block.covered || 0,
|
|
121
|
+
total: block.total || 0, ratio: ratio)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def render_licensing
|
|
126
|
+
return unless @report.licensing
|
|
127
|
+
|
|
128
|
+
l = @report.licensing
|
|
129
|
+
section("LICENSING")
|
|
130
|
+
row("Copyright", l.copyright)
|
|
131
|
+
row("Trademark", l.trademark)
|
|
132
|
+
row("Manufacturer", l.manufacturer)
|
|
133
|
+
row("Designer", l.designer)
|
|
134
|
+
row("License", l.license_description)
|
|
135
|
+
row("License URL", l.license_url)
|
|
136
|
+
row("Vendor URL", l.vendor_url)
|
|
137
|
+
row("Designer URL", l.designer_url)
|
|
138
|
+
row("Vendor ID", l.vendor_id)
|
|
139
|
+
row("Embedding", l.embedding_type)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def render_hinting
|
|
143
|
+
return unless @report.hinting
|
|
144
|
+
|
|
145
|
+
h = @report.hinting
|
|
146
|
+
section("HINTING")
|
|
147
|
+
row("Format", h.hinting_format || (h.is_unhinted ? "unhinted" : "unknown"))
|
|
148
|
+
row("fpgm", instruction_line(h.has_fpgm, h.fpgm_instruction_count))
|
|
149
|
+
row("prep", instruction_line(h.has_prep, h.prep_instruction_count))
|
|
150
|
+
row("cvt", cvt_line(h))
|
|
151
|
+
row("gasp", gasp_line(h))
|
|
152
|
+
row("CFF hints", h.cff_hint_count)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def render_color
|
|
156
|
+
return unless @report.color_capabilities
|
|
157
|
+
|
|
158
|
+
c = @report.color_capabilities
|
|
159
|
+
section("COLOR")
|
|
160
|
+
formats = Array(c.color_formats)
|
|
161
|
+
row("Color formats", formats.empty? ? "(none)" : truncate_list(formats))
|
|
162
|
+
row("COLR", colr_line(c)) if c.has_colr
|
|
163
|
+
row("CPAL", "palettes: #{c.cpal_palette_count}, colors: #{c.cpal_color_count}") if c.has_cpal
|
|
164
|
+
row("SVG documents", c.svg_document_count) if c.has_svg && c.svg_document_count
|
|
165
|
+
row("CBDT strikes", c.cbdt_strike_count) if c.has_cbdt && c.cbdt_strike_count
|
|
166
|
+
row("sbix strikes", c.sbix_strike_count) if c.has_sbix && c.sbix_strike_count
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def render_variation
|
|
170
|
+
v = @report.variation
|
|
171
|
+
section("VARIABLE FONT")
|
|
172
|
+
if v.nil? || Array(v.axes).empty?
|
|
173
|
+
@lines << " (not variable)"
|
|
174
|
+
return
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
v.axes.each do |axis|
|
|
178
|
+
row(axis.tag, format("%<min>s .. %<max>s default %<default>s",
|
|
179
|
+
min: axis.min_value, max: axis.max_value,
|
|
180
|
+
default: axis.default_value))
|
|
181
|
+
end
|
|
182
|
+
return if Array(v.named_instances).empty?
|
|
183
|
+
|
|
184
|
+
@lines << " Named instances:"
|
|
185
|
+
v.named_instances.each do |inst|
|
|
186
|
+
@lines << " #{inst.postscript_name || inst.subfamily_name}: #{inst.coordinates}"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def render_opentype_layout
|
|
191
|
+
return unless @report.opentype_layout
|
|
192
|
+
|
|
193
|
+
l = @report.opentype_layout
|
|
194
|
+
section("OPENTYPE LAYOUT")
|
|
195
|
+
row("GSUB", yes_no(l.has_gsub))
|
|
196
|
+
row("GPOS", yes_no(l.has_gpos))
|
|
197
|
+
row("Scripts (#{Array(l.scripts).size})", truncate_list(l.scripts))
|
|
198
|
+
row("Features (#{Array(l.features).size})", truncate_list(l.features))
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def render_language_coverage
|
|
202
|
+
langs = Array(@report.language_coverage)
|
|
203
|
+
return if langs.empty?
|
|
204
|
+
|
|
205
|
+
section("LANGUAGE COVERAGE (CLDR #{@report.cldr_version})")
|
|
206
|
+
langs.first(LIST_LIMIT).each do |lang|
|
|
207
|
+
pct = lang.coverage_ratio ? format("%<r>d%%", r: (lang.coverage_ratio * 100).round) : "?"
|
|
208
|
+
mark = lang.fully_supported ? "*" : " "
|
|
209
|
+
@lines << format(" %<mark>s %<lang>-8s %<covered>d/%<total>d (%<pct>s)",
|
|
210
|
+
mark: mark, lang: "#{lang.language}:", covered: lang.covered,
|
|
211
|
+
total: lang.total, pct: pct)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def render_warnings
|
|
216
|
+
section("WARNINGS")
|
|
217
|
+
@lines << if @report.warning
|
|
218
|
+
" #{@report.warning}"
|
|
219
|
+
else
|
|
220
|
+
" (none)"
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# ---- formatting helpers --------------------------------------------
|
|
225
|
+
|
|
226
|
+
def section(title)
|
|
227
|
+
@lines << ""
|
|
228
|
+
@lines << title
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def row(label, value)
|
|
232
|
+
return if value.nil?
|
|
233
|
+
return if value.is_a?(String) && value.empty?
|
|
234
|
+
|
|
235
|
+
@lines << " #{label}:#{' ' * [LABEL_WIDTH - label.to_s.length - 1, 1].max}#{value}"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def two_col(left_label, left_value, right_label, right_value)
|
|
239
|
+
left = "#{left_label} #{left_value}".ljust(40)
|
|
240
|
+
"#{left}#{right_label} #{right_value}"
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def yes_no(bool)
|
|
244
|
+
bool ? "yes" : "no"
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def truncate_list(items)
|
|
248
|
+
list = Array(items)
|
|
249
|
+
return "(none)" if list.empty?
|
|
250
|
+
|
|
251
|
+
shown = list.first(LIST_LIMIT).join(", ")
|
|
252
|
+
shown += ", ..." if list.size > LIST_LIMIT
|
|
253
|
+
shown
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def weight_descriptor
|
|
257
|
+
return nil unless @report.weight_class
|
|
258
|
+
|
|
259
|
+
name = weight_name(@report.weight_class)
|
|
260
|
+
"#{@report.weight_class}#{" (#{name})" if name}"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def width_descriptor
|
|
264
|
+
return nil unless @report.width_class
|
|
265
|
+
|
|
266
|
+
name = WIDTH_NAMES[@report.width_class]
|
|
267
|
+
"#{@report.width_class}#{" (#{name})" if name}"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def weight_name(value)
|
|
271
|
+
case value
|
|
272
|
+
when 100 then "Thin"
|
|
273
|
+
when 200 then "Extra-light"
|
|
274
|
+
when 300 then "Light"
|
|
275
|
+
when 400 then "Regular"
|
|
276
|
+
when 500 then "Medium"
|
|
277
|
+
when 600 then "Semi-bold"
|
|
278
|
+
when 700 then "Bold"
|
|
279
|
+
when 800 then "Extra-bold"
|
|
280
|
+
when 900 then "Black"
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def bbox_descriptor(metrics)
|
|
285
|
+
"(#{metrics.bbox_x_min}, #{metrics.bbox_y_min}) → (#{metrics.bbox_x_max}, #{metrics.bbox_y_max})"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def codepoint_range_preview
|
|
289
|
+
ranges = Array(@report.codepoint_ranges)
|
|
290
|
+
return "(none)" if ranges.empty?
|
|
291
|
+
|
|
292
|
+
shown = ranges.first(LIST_LIMIT).map do |r|
|
|
293
|
+
"U+#{format('%04X', r.first_cp)}-U+#{format('%04X', r.last_cp)}"
|
|
294
|
+
end.join(", ")
|
|
295
|
+
shown += ", ..." if ranges.size > LIST_LIMIT
|
|
296
|
+
shown
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def instruction_line(has, count)
|
|
300
|
+
return "no" unless has
|
|
301
|
+
|
|
302
|
+
count ? "#{count} instructions" : "present"
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def cvt_line(hinting)
|
|
306
|
+
return "no" unless hinting.has_cvt
|
|
307
|
+
|
|
308
|
+
hinting.cvt_entry_count ? "#{hinting.cvt_entry_count} entries" : "present"
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def gasp_line(hinting)
|
|
312
|
+
ranges = Array(hinting.gasp_ranges)
|
|
313
|
+
return "no" if ranges.empty?
|
|
314
|
+
|
|
315
|
+
ppems = ranges.map(&:max_ppem).compact
|
|
316
|
+
"#{ranges.size} ranges (#{ppems.join('/')} ppem)"
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def colr_line(color)
|
|
320
|
+
"v#{color.colr_version}, #{color.colr_base_glyph_count} base glyphs, #{color.colr_layer_count} layers"
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Formatters
|
|
5
|
+
# Human-readable overview of a {Models::Audit::LibrarySummary}.
|
|
6
|
+
#
|
|
7
|
+
# Lists the per-face rollup counts, aggregate metrics, script coverage
|
|
8
|
+
# matrix, duplicate groups, and license distribution. The full per-face
|
|
9
|
+
# AuditReports are attached to the model; this view only shows the
|
|
10
|
+
# cross-face summaries (use YAML/JSON output for the full per-face data).
|
|
11
|
+
class LibrarySummaryTextRenderer
|
|
12
|
+
SEPARATOR = "=" * 80
|
|
13
|
+
LIST_LIMIT = 15
|
|
14
|
+
|
|
15
|
+
# @param summary [Models::Audit::LibrarySummary]
|
|
16
|
+
def initialize(summary)
|
|
17
|
+
@summary = summary
|
|
18
|
+
@lines = []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [String]
|
|
22
|
+
def render
|
|
23
|
+
render_header
|
|
24
|
+
render_aggregates
|
|
25
|
+
render_script_coverage
|
|
26
|
+
render_duplicates
|
|
27
|
+
render_license_distribution
|
|
28
|
+
@lines.join("\n")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def render_header
|
|
34
|
+
@lines << "LIBRARY SUMMARY"
|
|
35
|
+
@lines << SEPARATOR
|
|
36
|
+
@lines << "root: #{@summary.root_path}"
|
|
37
|
+
@lines << "files: #{@summary.total_files} faces: #{@summary.total_faces}"
|
|
38
|
+
exts = Array(@summary.scanned_extensions)
|
|
39
|
+
@lines << "formats: #{exts.empty? ? '(none)' : exts.join(', ')}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def render_aggregates
|
|
43
|
+
m = @summary.aggregate_metrics || {}
|
|
44
|
+
section("AGGREGATES")
|
|
45
|
+
@lines << " codepoints: #{m[:total_codepoints] || 0}"
|
|
46
|
+
@lines << " glyphs: #{m[:total_glyphs] || 0}"
|
|
47
|
+
@lines << " total size: #{format_bytes(m[:total_size_bytes] || 0)}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def render_script_coverage
|
|
51
|
+
rows = Array(@summary.script_coverage)
|
|
52
|
+
return if rows.empty?
|
|
53
|
+
|
|
54
|
+
section("SCRIPT COVERAGE (top #{LIST_LIMIT})")
|
|
55
|
+
rows.first(LIST_LIMIT).each do |row|
|
|
56
|
+
@lines << " #{row.script}: #{row.face_count} face#{'s' unless row.face_count == 1}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def render_duplicates
|
|
61
|
+
groups = Array(@summary.duplicate_groups)
|
|
62
|
+
return if groups.empty?
|
|
63
|
+
|
|
64
|
+
section("DUPLICATES (#{groups.size} group#{'s' unless groups.size == 1})")
|
|
65
|
+
groups.each do |group|
|
|
66
|
+
@lines << " sha #{group.source_sha256[0, 12]}:"
|
|
67
|
+
group.files.each { |path| @lines << " #{path}" }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def render_license_distribution
|
|
72
|
+
dist = @summary.license_distribution || {}
|
|
73
|
+
return if dist.empty?
|
|
74
|
+
|
|
75
|
+
section("LICENSE DISTRIBUTION")
|
|
76
|
+
dist.sort_by { |_url, count| -count }.each do |url, count|
|
|
77
|
+
@lines << " #{count} #{url}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def section(title)
|
|
82
|
+
@lines << ""
|
|
83
|
+
@lines << title
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def format_bytes(bytes)
|
|
87
|
+
return "0 B" if bytes.nil? || bytes.zero?
|
|
88
|
+
|
|
89
|
+
if bytes < 1024
|
|
90
|
+
"#{bytes} B"
|
|
91
|
+
elsif bytes < 1024 * 1024
|
|
92
|
+
"#{(bytes / 1024.0).round(2)} KB"
|
|
93
|
+
else
|
|
94
|
+
"#{(bytes / (1024.0 * 1024)).round(2)} MB"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -44,6 +44,12 @@ module Fontisan
|
|
|
44
44
|
format_collection_info(model)
|
|
45
45
|
when Models::CollectionBriefInfo
|
|
46
46
|
format_collection_brief_info(model)
|
|
47
|
+
when Models::Audit::AuditReport
|
|
48
|
+
Formatters::AuditTextRenderer.new(model).render
|
|
49
|
+
when Models::Audit::AuditDiff
|
|
50
|
+
Formatters::AuditDiffTextRenderer.new(model).render
|
|
51
|
+
when Models::Audit::LibrarySummary
|
|
52
|
+
Formatters::LibrarySummaryTextRenderer.new(model).render
|
|
47
53
|
else
|
|
48
54
|
model.to_s
|
|
49
55
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Autoload hub for the Fontisan::Formatters namespace.
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Formatters
|
|
7
|
+
autoload :AuditDiffTextRenderer, "fontisan/formatters/audit_diff_text_renderer"
|
|
8
|
+
autoload :AuditTextRenderer, "fontisan/formatters/audit_text_renderer"
|
|
9
|
+
autoload :LibrarySummaryTextRenderer, "fontisan/formatters/library_summary_text_renderer"
|
|
10
|
+
autoload :TextFormatter, "fontisan/formatters/text_formatter"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../models/hint"
|
|
4
3
|
require "json"
|
|
5
4
|
|
|
6
5
|
module Fontisan
|
|
@@ -80,9 +79,6 @@ module Fontisan
|
|
|
80
79
|
cff2_data = tables["CFF2"] || tables["CFF2 "]
|
|
81
80
|
|
|
82
81
|
begin
|
|
83
|
-
require_relative "../tables/cff2/table_reader"
|
|
84
|
-
require_relative "../tables/cff2/table_builder"
|
|
85
|
-
|
|
86
82
|
reader = Tables::Cff2::TableReader.new(cff2_data)
|
|
87
83
|
|
|
88
84
|
# Validate CFF2 version
|
|
@@ -131,11 +127,6 @@ module Fontisan
|
|
|
131
127
|
|
|
132
128
|
# Apply hints (both font-level and per-glyph)
|
|
133
129
|
begin
|
|
134
|
-
require_relative "../tables/cff/table_builder"
|
|
135
|
-
require_relative "../tables/cff/charstring_rebuilder"
|
|
136
|
-
require_relative "../tables/cff/hint_operation_injector"
|
|
137
|
-
require_relative "../tables/cff"
|
|
138
|
-
|
|
139
130
|
# Parse CFF binary data into Cff object if needed
|
|
140
131
|
cff_data = tables["CFF "]
|
|
141
132
|
cff_table = if cff_data.is_a?(Tables::Cff)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Autoload hub for the Fontisan::Hints namespace.
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Hints
|
|
7
|
+
autoload :HintConverter, "fontisan/hints/hint_converter"
|
|
8
|
+
autoload :HintValidator, "fontisan/hints/hint_validator"
|
|
9
|
+
autoload :PostScriptHintApplier, "fontisan/hints/postscript_hint_applier"
|
|
10
|
+
autoload :PostScriptHintExtractor, "fontisan/hints/postscript_hint_extractor"
|
|
11
|
+
autoload :TrueTypeHintApplier, "fontisan/hints/truetype_hint_applier"
|
|
12
|
+
autoload :TrueTypeHintExtractor, "fontisan/hints/truetype_hint_extractor"
|
|
13
|
+
autoload :TrueTypeInstructionAnalyzer, "fontisan/hints/truetype_instruction_analyzer"
|
|
14
|
+
autoload :TrueTypeInstructionGenerator, "fontisan/hints/truetype_instruction_generator"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Models
|
|
7
|
+
module Audit
|
|
8
|
+
# One fvar axis descriptor on an AuditReport.
|
|
9
|
+
#
|
|
10
|
+
# `min_value` / `default_value` / `max_value` are used (rather than
|
|
11
|
+
# `min` / `default` / `max`) to avoid colliding with Ruby's built-in
|
|
12
|
+
# `default` method on classes.
|
|
13
|
+
class AuditAxis < Lutaml::Model::Serializable
|
|
14
|
+
attribute :tag, :string
|
|
15
|
+
attribute :min_value, :float
|
|
16
|
+
attribute :default_value, :float
|
|
17
|
+
attribute :max_value, :float
|
|
18
|
+
attribute :name, :string
|
|
19
|
+
|
|
20
|
+
key_value do
|
|
21
|
+
map "tag", to: :tag
|
|
22
|
+
map "min_value", to: :min_value
|
|
23
|
+
map "default_value", to: :default_value
|
|
24
|
+
map "max_value", to: :max_value
|
|
25
|
+
map "name", to: :name
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|