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
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "parsers/dfont_parser"
|
|
4
|
-
require_relative "error"
|
|
5
|
-
require_relative "collection/shared_logic"
|
|
6
|
-
|
|
7
3
|
module Fontisan
|
|
8
4
|
# DfontCollection represents an Apple dfont suitcase containing multiple fonts
|
|
9
5
|
#
|
|
@@ -26,6 +22,35 @@ module Fontisan
|
|
|
26
22
|
class DfontCollection
|
|
27
23
|
include Collection::SharedLogic
|
|
28
24
|
|
|
25
|
+
# High-level pipeline format identifier. Owned by the collection class
|
|
26
|
+
# so the conversion pipeline can dispatch without case statements (OCP).
|
|
27
|
+
#
|
|
28
|
+
# @return [Symbol] :dfont
|
|
29
|
+
def format = :dfont
|
|
30
|
+
|
|
31
|
+
# Whether this object represents a font collection rather than a single
|
|
32
|
+
# font. Each font class is the authority on this question.
|
|
33
|
+
#
|
|
34
|
+
# @return [Boolean]
|
|
35
|
+
def collection? = true
|
|
36
|
+
|
|
37
|
+
# Variation profile. dfonts are containers; per-font variation is not
|
|
38
|
+
# exposed at the collection level.
|
|
39
|
+
#
|
|
40
|
+
# @return [Symbol] :static
|
|
41
|
+
def variation_type = :static
|
|
42
|
+
|
|
43
|
+
# Outline representation. dfonts may contain mixed-flavor fonts;
|
|
44
|
+
# per-font outline type requires loading an individual font.
|
|
45
|
+
#
|
|
46
|
+
# @return [Symbol] :unknown
|
|
47
|
+
def outline_type = :unknown
|
|
48
|
+
|
|
49
|
+
# Collections have no single SFNT table directory.
|
|
50
|
+
#
|
|
51
|
+
# @return [Array<String>] empty
|
|
52
|
+
def table_names = []
|
|
53
|
+
|
|
29
54
|
# Path to dfont file
|
|
30
55
|
# @return [String]
|
|
31
56
|
attr_reader :path
|
|
@@ -138,9 +163,6 @@ module Fontisan
|
|
|
138
163
|
# @param io [IO] Open file handle
|
|
139
164
|
# @return [Models::CollectionListInfo] Collection list info
|
|
140
165
|
def list_fonts(io)
|
|
141
|
-
require_relative "models/collection_list_info"
|
|
142
|
-
require_relative "models/collection_font_summary"
|
|
143
|
-
|
|
144
166
|
fonts = extract_fonts(io)
|
|
145
167
|
|
|
146
168
|
summaries = fonts.map.with_index do |font, index|
|
|
@@ -227,9 +249,6 @@ module Fontisan
|
|
|
227
249
|
# puts "Format: #{info.collection_format}"
|
|
228
250
|
# end
|
|
229
251
|
def collection_info(io, path)
|
|
230
|
-
require_relative "models/collection_info"
|
|
231
|
-
require_relative "models/table_sharing_info"
|
|
232
|
-
|
|
233
252
|
# Calculate table sharing statistics
|
|
234
253
|
table_sharing = calculate_table_sharing(io)
|
|
235
254
|
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../models/font_export"
|
|
4
|
-
require_relative "table_serializer"
|
|
5
|
-
require_relative "ttx_generator"
|
|
6
|
-
require_relative "transformers/font_to_ttx"
|
|
7
|
-
require_relative "../utilities/checksum_calculator"
|
|
8
|
-
|
|
9
3
|
module Fontisan
|
|
10
4
|
module Export
|
|
11
5
|
# Exporter orchestrates font export to YAML/JSON/TTX
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../../models/ttx/ttfont"
|
|
4
|
-
require_relative "../../models/ttx/glyph_order"
|
|
5
|
-
require_relative "head_transformer"
|
|
6
|
-
require_relative "name_transformer"
|
|
7
|
-
require_relative "os2_transformer"
|
|
8
|
-
require_relative "post_transformer"
|
|
9
|
-
require_relative "hhea_transformer"
|
|
10
|
-
require_relative "maxp_transformer"
|
|
11
|
-
|
|
12
3
|
module Fontisan
|
|
13
4
|
module Export
|
|
14
5
|
module Transformers
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Autoload hub for the Fontisan::Export::Transformers namespace.
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Export
|
|
7
|
+
module Transformers
|
|
8
|
+
autoload :FontToTtx, "fontisan/export/transformers/font_to_ttx"
|
|
9
|
+
autoload :HeadTransformer, "fontisan/export/transformers/head_transformer"
|
|
10
|
+
autoload :HheaTransformer, "fontisan/export/transformers/hhea_transformer"
|
|
11
|
+
autoload :MaxpTransformer, "fontisan/export/transformers/maxp_transformer"
|
|
12
|
+
autoload :NameTransformer, "fontisan/export/transformers/name_transformer"
|
|
13
|
+
autoload :Os2Transformer, "fontisan/export/transformers/os2_transformer"
|
|
14
|
+
autoload :PostTransformer, "fontisan/export/transformers/post_transformer"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Autoload hub for the Fontisan::Export namespace.
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Export
|
|
7
|
+
autoload :Exporter, "fontisan/export/exporter"
|
|
8
|
+
autoload :TableSerializer, "fontisan/export/table_serializer"
|
|
9
|
+
autoload :Transformers, "fontisan/export/transformers/font_to_ttx"
|
|
10
|
+
autoload :TtxGenerator, "fontisan/export/ttx_generator"
|
|
11
|
+
autoload :TtxParser, "fontisan/export/ttx_parser"
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/fontisan/font_loader.rb
CHANGED
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "stringio"
|
|
4
|
-
require_relative "constants"
|
|
5
|
-
require_relative "loading_modes"
|
|
6
|
-
require_relative "true_type_font"
|
|
7
|
-
require_relative "open_type_font"
|
|
8
|
-
require_relative "true_type_collection"
|
|
9
|
-
require_relative "open_type_collection"
|
|
10
|
-
require_relative "dfont_collection"
|
|
11
|
-
require_relative "woff_font"
|
|
12
|
-
require_relative "woff2_font"
|
|
13
|
-
require_relative "type1_font"
|
|
14
|
-
require_relative "parsers/dfont_parser"
|
|
15
|
-
require_relative "error"
|
|
16
4
|
|
|
17
5
|
module Fontisan
|
|
18
6
|
# FontLoader provides unified font loading with content-based format detection.
|
|
@@ -85,12 +73,18 @@ module Fontisan
|
|
|
85
73
|
|
|
86
74
|
format = detect(path)
|
|
87
75
|
case format
|
|
88
|
-
when :ttf then TrueTypeFont.from_file(path, mode: resolved_mode,
|
|
89
|
-
|
|
90
|
-
when :
|
|
91
|
-
|
|
92
|
-
when :
|
|
93
|
-
|
|
76
|
+
when :ttf then TrueTypeFont.from_file(path, mode: resolved_mode,
|
|
77
|
+
lazy: resolved_lazy)
|
|
78
|
+
when :otf then OpenTypeFont.from_file(path, mode: resolved_mode,
|
|
79
|
+
lazy: resolved_lazy)
|
|
80
|
+
when :woff then WoffFont.from_file(path, mode: resolved_mode,
|
|
81
|
+
lazy: resolved_lazy)
|
|
82
|
+
when :woff2 then Woff2Font.from_file(path, mode: resolved_mode,
|
|
83
|
+
lazy: resolved_lazy)
|
|
84
|
+
when :ttc, :otc then load_from_collection(path, format, font_index,
|
|
85
|
+
mode: resolved_mode)
|
|
86
|
+
when :dfont then load_dfont(path, font_index: font_index,
|
|
87
|
+
mode: resolved_mode)
|
|
94
88
|
when :pfa, :pfb then Type1Font.from_file(path, mode: resolved_mode)
|
|
95
89
|
else
|
|
96
90
|
raise InvalidFontError,
|
|
@@ -279,7 +273,8 @@ module Fontisan
|
|
|
279
273
|
# public `lazy:` flag is not threaded through this path.
|
|
280
274
|
def self.load_dfont(path, font_index:, mode:)
|
|
281
275
|
File.open(path, "rb") do |io|
|
|
282
|
-
sfnt_io = StringIO.new(Parsers::DfontParser.extract_sfnt(io,
|
|
276
|
+
sfnt_io = StringIO.new(Parsers::DfontParser.extract_sfnt(io,
|
|
277
|
+
index: font_index))
|
|
283
278
|
klass = case Constants.sfnt_format_for(sfnt_io.read(4))
|
|
284
279
|
when :ttf then TrueTypeFont
|
|
285
280
|
when :otf then OpenTypeFont
|
data/lib/fontisan/font_writer.rb
CHANGED
|
@@ -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
|