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,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Models
|
|
7
|
+
module Audit
|
|
8
|
+
# Group of files that share an identical `source_sha256`.
|
|
9
|
+
#
|
|
10
|
+
# Detecting duplicate byte-identical files (regardless of filename)
|
|
11
|
+
# is the cheapest form of library hygiene: same bytes = same font.
|
|
12
|
+
class DuplicateGroup < Lutaml::Model::Serializable
|
|
13
|
+
attribute :source_sha256, :string
|
|
14
|
+
attribute :files, :string, collection: true
|
|
15
|
+
|
|
16
|
+
key_value do
|
|
17
|
+
map "source_sha256", to: :source_sha256
|
|
18
|
+
map "files", to: :files
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Models
|
|
7
|
+
module Audit
|
|
8
|
+
# Decoded OS/2 fsType bitfield → canonical embedding-permission string.
|
|
9
|
+
#
|
|
10
|
+
# Per OpenType spec, fsType is a bitfield. Only one of bits 0-3 should
|
|
11
|
+
# be set (the basic permission level); bits 4-7 are modifiers that
|
|
12
|
+
# only apply when INSTALLABLE (bit 3) is set.
|
|
13
|
+
#
|
|
14
|
+
# The decoder normalizes to one of seven canonical strings so
|
|
15
|
+
# downstream consumers don't need to know the bit layout.
|
|
16
|
+
class EmbeddingType < Lutaml::Model::Serializable
|
|
17
|
+
# Bit masks (OpenType fsType bitfield).
|
|
18
|
+
RESTRICTED_LICENSE_NO_EMBEDDING = 0x0001
|
|
19
|
+
PREVIEW_AND_PRINT = 0x0002
|
|
20
|
+
EDITABLE_EMBEDDING = 0x0004
|
|
21
|
+
INSTALLABLE_EMBEDDING = 0x0008
|
|
22
|
+
NO_SUBSETTING = 0x0100
|
|
23
|
+
BITMAP_EMBEDDING_ONLY = 0x0200
|
|
24
|
+
|
|
25
|
+
attribute :value, :string
|
|
26
|
+
|
|
27
|
+
# Decoded canonical string for the given fsType bitfield.
|
|
28
|
+
#
|
|
29
|
+
# @param fs_type [Integer, nil] raw OS/2 fsType value
|
|
30
|
+
# @return [String, nil] canonical permission name, or nil when
|
|
31
|
+
# fs_type is nil
|
|
32
|
+
def self.decode(fs_type)
|
|
33
|
+
return nil if fs_type.nil?
|
|
34
|
+
|
|
35
|
+
if fs_type & RESTRICTED_LICENSE_NO_EMBEDDING != 0
|
|
36
|
+
"restricted_license"
|
|
37
|
+
elsif fs_type & PREVIEW_AND_PRINT != 0
|
|
38
|
+
"preview_print"
|
|
39
|
+
elsif fs_type & EDITABLE_EMBEDDING != 0
|
|
40
|
+
"editable"
|
|
41
|
+
elsif fs_type & INSTALLABLE_EMBEDDING != 0
|
|
42
|
+
installable_subcategory(fs_type)
|
|
43
|
+
elsif fs_type.zero?
|
|
44
|
+
"installable"
|
|
45
|
+
else
|
|
46
|
+
"unknown"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Construct from a decoded canonical string.
|
|
51
|
+
#
|
|
52
|
+
# @param fs_type [Integer, nil]
|
|
53
|
+
def self.from_fs_type(fs_type)
|
|
54
|
+
new(value: decode(fs_type))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_s
|
|
58
|
+
value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.installable_subcategory(fs_type)
|
|
62
|
+
if fs_type & NO_SUBSETTING != 0 && fs_type & BITMAP_EMBEDDING_ONLY != 0
|
|
63
|
+
"installable_no_subsetting_bitmap_only"
|
|
64
|
+
elsif fs_type & NO_SUBSETTING != 0
|
|
65
|
+
"installable_no_subsetting"
|
|
66
|
+
elsif fs_type & BITMAP_EMBEDDING_ONLY != 0
|
|
67
|
+
"installable_bitmap_only"
|
|
68
|
+
else
|
|
69
|
+
"installable"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
private_class_method :installable_subcategory
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Models
|
|
7
|
+
module Audit
|
|
8
|
+
# One scalar field that differs between two AuditReports.
|
|
9
|
+
#
|
|
10
|
+
# `field` is the dotted attribute name (e.g. "weight_class").
|
|
11
|
+
# `left`/`right` are stringified values: nil → "", String → itself,
|
|
12
|
+
# anything else → its YAML form. Comparing the YAML form of nested
|
|
13
|
+
# models is intentionally avoided here — those diffs surface as
|
|
14
|
+
# structural add/remove lists on AuditDiff itself.
|
|
15
|
+
class FieldChange < Lutaml::Model::Serializable
|
|
16
|
+
attribute :field, :string
|
|
17
|
+
attribute :left, :string
|
|
18
|
+
attribute :right, :string
|
|
19
|
+
|
|
20
|
+
key_value do
|
|
21
|
+
map "field", to: :field
|
|
22
|
+
map "left", to: :left
|
|
23
|
+
map "right", to: :right
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Models
|
|
7
|
+
module Audit
|
|
8
|
+
# Decoded OS/2 fsSelection bitfield → sorted array of flag names.
|
|
9
|
+
#
|
|
10
|
+
# Per OpenType spec, fsSelection is a bitfield with these bits:
|
|
11
|
+
#
|
|
12
|
+
# bit 0 (0x001): italic
|
|
13
|
+
# bit 1 (0x002): underscore
|
|
14
|
+
# bit 2 (0x004): negative
|
|
15
|
+
# bit 3 (0x008): outlined
|
|
16
|
+
# bit 4 (0x010): strikeout
|
|
17
|
+
# bit 5 (0x020): bold
|
|
18
|
+
# bit 6 (0x040): regular
|
|
19
|
+
# bit 7 (0x080): use_typo_metrics
|
|
20
|
+
# bit 8 (0x100): wws
|
|
21
|
+
# bit 9 (0x200): oblique
|
|
22
|
+
#
|
|
23
|
+
# Returns names in spec order (bit ascending).
|
|
24
|
+
class FsSelectionFlags < Lutaml::Model::Serializable
|
|
25
|
+
FLAGS = {
|
|
26
|
+
0x001 => "italic",
|
|
27
|
+
0x002 => "underscore",
|
|
28
|
+
0x004 => "negative",
|
|
29
|
+
0x008 => "outlined",
|
|
30
|
+
0x010 => "strikeout",
|
|
31
|
+
0x020 => "bold",
|
|
32
|
+
0x040 => "regular",
|
|
33
|
+
0x080 => "use_typo_metrics",
|
|
34
|
+
0x100 => "wws",
|
|
35
|
+
0x200 => "oblique",
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
attribute :flags, :string, collection: true
|
|
39
|
+
|
|
40
|
+
# Decoded array of flag names in spec order (bit ascending).
|
|
41
|
+
#
|
|
42
|
+
# @param fs_selection [Integer, nil] raw OS/2 fsSelection value
|
|
43
|
+
# @return [Array<String>, nil]
|
|
44
|
+
def self.decode(fs_selection)
|
|
45
|
+
return nil if fs_selection.nil?
|
|
46
|
+
|
|
47
|
+
FLAGS.each_with_object([]) do |(mask, name), acc|
|
|
48
|
+
acc << name if fs_selection & mask != 0
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Construct from a raw fsSelection value.
|
|
53
|
+
#
|
|
54
|
+
# @param fs_selection [Integer, nil]
|
|
55
|
+
def self.from_fs_selection(fs_selection)
|
|
56
|
+
new(flags: decode(fs_selection))
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Models
|
|
7
|
+
module Audit
|
|
8
|
+
# One entry from the TrueType `gasp` (Grid-fitting And Scan-conversion
|
|
9
|
+
# Procedure) table.
|
|
10
|
+
#
|
|
11
|
+
# Each entry describes the hinting/smoothing policy that applies up to
|
|
12
|
+
# the given `max_ppem` (pixels-per-em). The OpenType spec defines four
|
|
13
|
+
# single-bit flags; the high 12 bits of the raw rangeFlags uint16 are
|
|
14
|
+
# reserved.
|
|
15
|
+
#
|
|
16
|
+
# Construct via {.from_flags} from the raw uint16 pair; never hand-build
|
|
17
|
+
# the bit decoding at call sites.
|
|
18
|
+
class GaspRange < Lutaml::Model::Serializable
|
|
19
|
+
# OpenType gasp rangeFlags bit masks.
|
|
20
|
+
GRIDFIT = 0x0001
|
|
21
|
+
DO_GRAY = 0x0002
|
|
22
|
+
SYMMETRIC_GRIDFIT = 0x0004
|
|
23
|
+
SYMMETRIC_SMOOTHING = 0x0008
|
|
24
|
+
|
|
25
|
+
attribute :max_ppem, :integer
|
|
26
|
+
attribute :gridfit, Lutaml::Model::Type::Boolean
|
|
27
|
+
attribute :do_gray, Lutaml::Model::Type::Boolean
|
|
28
|
+
attribute :symmetric_gridfit, Lutaml::Model::Type::Boolean
|
|
29
|
+
attribute :symmetric_smoothing, Lutaml::Model::Type::Boolean
|
|
30
|
+
|
|
31
|
+
key_value do
|
|
32
|
+
map "max_ppem", to: :max_ppem
|
|
33
|
+
map "gridfit", to: :gridfit
|
|
34
|
+
map "do_gray", to: :do_gray
|
|
35
|
+
map "symmetric_gridfit", to: :symmetric_gridfit
|
|
36
|
+
map "symmetric_smoothing", to: :symmetric_smoothing
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Build a GaspRange from the raw uint16 pair stored in the gasp table.
|
|
40
|
+
#
|
|
41
|
+
# @param max_ppem [Integer] rangeMaxPPEM (exclusive upper bound)
|
|
42
|
+
# @param flags [Integer] raw rangeFlags bitfield
|
|
43
|
+
# @return [GaspRange]
|
|
44
|
+
def self.from_flags(max_ppem, flags)
|
|
45
|
+
new(
|
|
46
|
+
max_ppem: max_ppem,
|
|
47
|
+
gridfit: (flags & GRIDFIT).positive?,
|
|
48
|
+
do_gray: (flags & DO_GRAY).positive?,
|
|
49
|
+
symmetric_gridfit: (flags & SYMMETRIC_GRIDFIT).positive?,
|
|
50
|
+
symmetric_smoothing: (flags & SYMMETRIC_SMOOTHING).positive?,
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Derived: both gridfit and do_gray are set. Mac historically treated
|
|
55
|
+
# this combination as "do everything". Not serialized — compute on
|
|
56
|
+
# demand.
|
|
57
|
+
def gridfit_and_smoothing?
|
|
58
|
+
gridfit && do_gray
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Models
|
|
7
|
+
module Audit
|
|
8
|
+
# Hinting summary for one face.
|
|
9
|
+
#
|
|
10
|
+
# Answers the practical questions a designer or QA engineer asks:
|
|
11
|
+
# "Is this font hinted at all? What flavour? How much hinting, by
|
|
12
|
+
# byte / instruction count?" Unhinted fonts render poorly at small
|
|
13
|
+
# sizes; heavily hinted fonts can be 20%+ bytecode by file size.
|
|
14
|
+
#
|
|
15
|
+
# TrueType hinting surfaces as the fpgm/prep/cvt programs plus the
|
|
16
|
+
# gasp per-ppem policy. CFF/CFF2 hinting surfaces as stem hints
|
|
17
|
+
# encoded inside each CharString. This model carries both, plus a
|
|
18
|
+
# derived `is_unhinted` flag and `hinting_format` classification so
|
|
19
|
+
# downstream tooling does not need to re-derive either.
|
|
20
|
+
#
|
|
21
|
+
# All counts are nil-safe: a face with no hinting at all produces
|
|
22
|
+
# `Hinting.new` with every field falsy/nil rather than raising.
|
|
23
|
+
class Hinting < Lutaml::Model::Serializable
|
|
24
|
+
# TrueType bytecode programs.
|
|
25
|
+
attribute :has_fpgm, Lutaml::Model::Type::Boolean
|
|
26
|
+
attribute :fpgm_instruction_count, :integer
|
|
27
|
+
attribute :has_prep, Lutaml::Model::Type::Boolean
|
|
28
|
+
attribute :prep_instruction_count, :integer
|
|
29
|
+
|
|
30
|
+
# TrueType Control Value Table (hinting metrics).
|
|
31
|
+
attribute :has_cvt, Lutaml::Model::Type::Boolean
|
|
32
|
+
attribute :cvt_entry_count, :integer
|
|
33
|
+
|
|
34
|
+
# CVT variation table for variable TrueType fonts. Carried for
|
|
35
|
+
# context only — never included in cvt_entry_count.
|
|
36
|
+
attribute :has_cvar, Lutaml::Model::Type::Boolean
|
|
37
|
+
|
|
38
|
+
# gasp policy ranges, ordered by ascending max_ppem.
|
|
39
|
+
attribute :gasp_ranges, GaspRange, collection: true
|
|
40
|
+
|
|
41
|
+
# CFF/CFF2 hinting. cff_has_private_dict is true for every CFF
|
|
42
|
+
# face (Private DICT is mandatory); cff_hint_count sums stem
|
|
43
|
+
# declarations across all CharStrings, nil when unparsable.
|
|
44
|
+
attribute :cff_has_private_dict, Lutaml::Model::Type::Boolean
|
|
45
|
+
attribute :cff_hint_count, :integer
|
|
46
|
+
|
|
47
|
+
# Derived at extraction time so consumers read flat fields.
|
|
48
|
+
attribute :is_unhinted, Lutaml::Model::Type::Boolean
|
|
49
|
+
attribute :hinting_format, :string
|
|
50
|
+
|
|
51
|
+
key_value do
|
|
52
|
+
map "has_fpgm", to: :has_fpgm
|
|
53
|
+
map "fpgm_instruction_count", to: :fpgm_instruction_count
|
|
54
|
+
map "has_prep", to: :has_prep
|
|
55
|
+
map "prep_instruction_count", to: :prep_instruction_count
|
|
56
|
+
map "has_cvt", to: :has_cvt
|
|
57
|
+
map "cvt_entry_count", to: :cvt_entry_count
|
|
58
|
+
map "has_cvar", to: :has_cvar
|
|
59
|
+
map "gasp_ranges", to: :gasp_ranges
|
|
60
|
+
map "cff_has_private_dict", to: :cff_has_private_dict
|
|
61
|
+
map "cff_hint_count", to: :cff_hint_count
|
|
62
|
+
map "is_unhinted", to: :is_unhinted
|
|
63
|
+
map "hinting_format", to: :hinting_format
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
FORMAT_TRUETYPE = "truetype"
|
|
67
|
+
FORMAT_CFF = "cff"
|
|
68
|
+
FORMAT_MIXED = "mixed"
|
|
69
|
+
FORMAT_NONE = "none"
|
|
70
|
+
|
|
71
|
+
# Derive {is_unhinted} and {hinting_format} from individual flags.
|
|
72
|
+
# Called by the extractor before construction so the values land
|
|
73
|
+
# in serialized output without recomputation at read time.
|
|
74
|
+
#
|
|
75
|
+
# gasp is a TrueType-specific table, so it counts toward the
|
|
76
|
+
# TrueType hinting bucket even when no fpgm/prep/cvt is present.
|
|
77
|
+
#
|
|
78
|
+
# @return [Hash] keys :is_unhinted, :hinting_format
|
|
79
|
+
def self.derive_flags(has_tt:, has_cff:, has_gasp:)
|
|
80
|
+
tt_hints = has_tt || has_gasp
|
|
81
|
+
any = tt_hints || has_cff
|
|
82
|
+
format =
|
|
83
|
+
if tt_hints && has_cff then FORMAT_MIXED
|
|
84
|
+
elsif tt_hints then FORMAT_TRUETYPE
|
|
85
|
+
elsif has_cff then FORMAT_CFF
|
|
86
|
+
else FORMAT_NONE
|
|
87
|
+
end
|
|
88
|
+
{ is_unhinted: !any, hinting_format: format }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Models
|
|
7
|
+
module Audit
|
|
8
|
+
# Aggregate view over a directory (tree) of audited fonts.
|
|
9
|
+
#
|
|
10
|
+
# Built by {Audit::LibraryAuditor}. Combines a flat list of
|
|
11
|
+
# per-face {AuditReport}s with derived cross-face rollups:
|
|
12
|
+
# script coverage matrix, duplicate detection (by source_sha256),
|
|
13
|
+
# and license distribution. Lets a librarian inventory a font
|
|
14
|
+
# collection in one pass.
|
|
15
|
+
class LibrarySummary < Lutaml::Model::Serializable
|
|
16
|
+
attribute :root_path, :string
|
|
17
|
+
attribute :total_files, :integer
|
|
18
|
+
attribute :total_faces, :integer
|
|
19
|
+
attribute :scanned_extensions, :string, collection: true
|
|
20
|
+
attribute :aggregate_metrics, :hash
|
|
21
|
+
attribute :script_coverage, ScriptCoverageRow, collection: true
|
|
22
|
+
attribute :duplicate_groups, DuplicateGroup, collection: true
|
|
23
|
+
attribute :license_distribution, :hash
|
|
24
|
+
attribute :per_face_reports, AuditReport, collection: true
|
|
25
|
+
|
|
26
|
+
key_value do
|
|
27
|
+
map "root_path", to: :root_path
|
|
28
|
+
map "total_files", to: :total_files
|
|
29
|
+
map "total_faces", to: :total_faces
|
|
30
|
+
map "scanned_extensions", to: :scanned_extensions
|
|
31
|
+
map "aggregate_metrics", to: :aggregate_metrics
|
|
32
|
+
map "script_coverage", to: :script_coverage
|
|
33
|
+
map "duplicate_groups", to: :duplicate_groups
|
|
34
|
+
map "license_distribution", to: :license_distribution
|
|
35
|
+
map "per_face_reports", to: :per_face_reports
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Models
|
|
7
|
+
module Audit
|
|
8
|
+
# Licensing + embedding + vendor provenance fields for a face.
|
|
9
|
+
#
|
|
10
|
+
# Combines the human-readable legal/identity fields from the name
|
|
11
|
+
# table with the machine-readable embedding permissions from OS/2.
|
|
12
|
+
# Type 1 fonts have no OS/2 — callers must tolerate a nil
|
|
13
|
+
# embedding_type / fs_selection_flags / vendor_id.
|
|
14
|
+
class Licensing < Lutaml::Model::Serializable
|
|
15
|
+
# Name-table fields (English name IDs)
|
|
16
|
+
attribute :copyright, :string
|
|
17
|
+
attribute :trademark, :string
|
|
18
|
+
attribute :manufacturer, :string
|
|
19
|
+
attribute :designer, :string
|
|
20
|
+
attribute :description, :string
|
|
21
|
+
attribute :vendor_url, :string
|
|
22
|
+
attribute :designer_url, :string
|
|
23
|
+
attribute :license_description, :string
|
|
24
|
+
attribute :license_url, :string
|
|
25
|
+
|
|
26
|
+
# OS/2 fields
|
|
27
|
+
attribute :vendor_id, :string
|
|
28
|
+
attribute :embedding_type, :string
|
|
29
|
+
attribute :fs_selection_flags, :string, collection: true
|
|
30
|
+
|
|
31
|
+
key_value do
|
|
32
|
+
map "copyright", to: :copyright
|
|
33
|
+
map "trademark", to: :trademark
|
|
34
|
+
map "manufacturer", to: :manufacturer
|
|
35
|
+
map "designer", to: :designer
|
|
36
|
+
map "description", to: :description
|
|
37
|
+
map "vendor_url", to: :vendor_url
|
|
38
|
+
map "designer_url", to: :designer_url
|
|
39
|
+
map "license_description", to: :license_description
|
|
40
|
+
map "license_url", to: :license_url
|
|
41
|
+
map "vendor_id", to: :vendor_id
|
|
42
|
+
map "embedding_type", to: :embedding_type
|
|
43
|
+
map "fs_selection_flags", to: :fs_selection_flags
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Models
|
|
7
|
+
module Audit
|
|
8
|
+
# Layout-critical metrics for a face, consolidated from head, hhea,
|
|
9
|
+
# OS/2, and post tables. Designers and engineers can read all
|
|
10
|
+
# spacing-relevant numbers in one place instead of cross-referencing
|
|
11
|
+
# raw table dumps.
|
|
12
|
+
#
|
|
13
|
+
# All fields are nil-safe — Type 1 fonts and stripped WOFF builds
|
|
14
|
+
# may not carry every table. Derived booleans (e.g. metrics_consistent?)
|
|
15
|
+
# tolerate nil inputs and return false rather than raising.
|
|
16
|
+
class Metrics < Lutaml::Model::Serializable
|
|
17
|
+
# head
|
|
18
|
+
attribute :units_per_em, :integer
|
|
19
|
+
attribute :bbox_x_min, :integer
|
|
20
|
+
attribute :bbox_y_min, :integer
|
|
21
|
+
attribute :bbox_x_max, :integer
|
|
22
|
+
attribute :bbox_y_max, :integer
|
|
23
|
+
|
|
24
|
+
# hhea (horizontal)
|
|
25
|
+
attribute :hhea_ascent, :integer
|
|
26
|
+
attribute :hhea_descent, :integer
|
|
27
|
+
attribute :hhea_line_gap, :integer
|
|
28
|
+
|
|
29
|
+
# OS/2 typo
|
|
30
|
+
attribute :typo_ascender, :integer
|
|
31
|
+
attribute :typo_descender, :integer
|
|
32
|
+
attribute :typo_line_gap, :integer
|
|
33
|
+
|
|
34
|
+
# OS/2 win
|
|
35
|
+
attribute :win_ascent, :integer
|
|
36
|
+
attribute :win_descent, :integer
|
|
37
|
+
|
|
38
|
+
# OS/2 v2+ (optional)
|
|
39
|
+
attribute :x_height, :integer
|
|
40
|
+
attribute :cap_height, :integer
|
|
41
|
+
|
|
42
|
+
# OS/2 subscript/superscript
|
|
43
|
+
attribute :subscript_x_size, :integer
|
|
44
|
+
attribute :subscript_y_size, :integer
|
|
45
|
+
attribute :subscript_x_offset, :integer
|
|
46
|
+
attribute :subscript_y_offset, :integer
|
|
47
|
+
attribute :superscript_x_size, :integer
|
|
48
|
+
attribute :superscript_y_size, :integer
|
|
49
|
+
attribute :superscript_x_offset, :integer
|
|
50
|
+
attribute :superscript_y_offset, :integer
|
|
51
|
+
|
|
52
|
+
# OS/2 strikeout
|
|
53
|
+
attribute :strikeout_size, :integer
|
|
54
|
+
attribute :strikeout_position, :integer
|
|
55
|
+
|
|
56
|
+
# post underline
|
|
57
|
+
attribute :underline_position, :float
|
|
58
|
+
attribute :underline_thickness, :float
|
|
59
|
+
|
|
60
|
+
key_value do
|
|
61
|
+
map "units_per_em", to: :units_per_em
|
|
62
|
+
map "bbox_x_min", to: :bbox_x_min
|
|
63
|
+
map "bbox_y_min", to: :bbox_y_min
|
|
64
|
+
map "bbox_x_max", to: :bbox_x_max
|
|
65
|
+
map "bbox_y_max", to: :bbox_y_max
|
|
66
|
+
|
|
67
|
+
map "hhea_ascent", to: :hhea_ascent
|
|
68
|
+
map "hhea_descent", to: :hhea_descent
|
|
69
|
+
map "hhea_line_gap", to: :hhea_line_gap
|
|
70
|
+
|
|
71
|
+
map "typo_ascender", to: :typo_ascender
|
|
72
|
+
map "typo_descender", to: :typo_descender
|
|
73
|
+
map "typo_line_gap", to: :typo_line_gap
|
|
74
|
+
|
|
75
|
+
map "win_ascent", to: :win_ascent
|
|
76
|
+
map "win_descent", to: :win_descent
|
|
77
|
+
|
|
78
|
+
map "x_height", to: :x_height
|
|
79
|
+
map "cap_height", to: :cap_height
|
|
80
|
+
|
|
81
|
+
map "subscript_x_size", to: :subscript_x_size
|
|
82
|
+
map "subscript_y_size", to: :subscript_y_size
|
|
83
|
+
map "subscript_x_offset", to: :subscript_x_offset
|
|
84
|
+
map "subscript_y_offset", to: :subscript_y_offset
|
|
85
|
+
map "superscript_x_size", to: :superscript_x_size
|
|
86
|
+
map "superscript_y_size", to: :superscript_y_size
|
|
87
|
+
map "superscript_x_offset", to: :superscript_x_offset
|
|
88
|
+
map "superscript_y_offset", to: :superscript_y_offset
|
|
89
|
+
|
|
90
|
+
map "strikeout_size", to: :strikeout_size
|
|
91
|
+
map "strikeout_position", to: :strikeout_position
|
|
92
|
+
|
|
93
|
+
map "underline_position", to: :underline_position
|
|
94
|
+
map "underline_thickness", to: :underline_thickness
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# True when hhea ascent/descent match OS/2 typo ascent/descent.
|
|
98
|
+
# Mismatch is a common font bug that causes inconsistent line
|
|
99
|
+
# height across platforms.
|
|
100
|
+
#
|
|
101
|
+
# @return [Boolean]
|
|
102
|
+
def metrics_consistent?
|
|
103
|
+
return false if hhea_ascent.nil? || typo_ascender.nil?
|
|
104
|
+
return false if hhea_descent.nil? || typo_descender.nil?
|
|
105
|
+
|
|
106
|
+
hhea_ascent == typo_ascender && hhea_descent == typo_descender
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Models
|
|
7
|
+
module Audit
|
|
8
|
+
# One fvar named instance (e.g. "Bold", "SemiCondensed").
|
|
9
|
+
#
|
|
10
|
+
# `coordinates` is serialized as a compact "tag=value,tag=value" string
|
|
11
|
+
# (e.g. "wght=700,wdth=100") for human readability. The AuditReport is
|
|
12
|
+
# primarily a human-facing artifact; downstream tooling that needs
|
|
13
|
+
# structured coordinates can re-derive them from fvar.
|
|
14
|
+
class NamedInstance < Lutaml::Model::Serializable
|
|
15
|
+
attribute :subfamily_name, :string
|
|
16
|
+
attribute :postscript_name, :string
|
|
17
|
+
attribute :coordinates, :string
|
|
18
|
+
|
|
19
|
+
key_value do
|
|
20
|
+
map "subfamily_name", to: :subfamily_name
|
|
21
|
+
map "postscript_name", to: :postscript_name
|
|
22
|
+
map "coordinates", to: :coordinates
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Build the coordinates string from a parallel array of axis tags
|
|
26
|
+
# and fvar coordinate values. Returns nil if either side is empty.
|
|
27
|
+
#
|
|
28
|
+
# @param axis_tags [Array<String>] ordered axis tags (e.g. ["wght", "wdth"])
|
|
29
|
+
# @param values [Array<Numeric>] ordered coordinate values
|
|
30
|
+
# @return [String, nil]
|
|
31
|
+
def self.format_coordinates(axis_tags, values)
|
|
32
|
+
return nil if axis_tags.nil? || values.nil?
|
|
33
|
+
return nil if axis_tags.empty? || values.empty?
|
|
34
|
+
|
|
35
|
+
pairs = axis_tags.zip(values).map { |tag, val| "#{tag}=#{val}" }
|
|
36
|
+
pairs.join(",")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Models
|
|
7
|
+
module Audit
|
|
8
|
+
# Structured OpenType layout summary for one face.
|
|
9
|
+
#
|
|
10
|
+
# Replaces the previous flat `opentype_scripts` + `features` pair
|
|
11
|
+
# on AuditReport for MECE cleanliness. Carries:
|
|
12
|
+
#
|
|
13
|
+
# - `scripts`: union of GSUB + GPOS script tags (sorted, unique).
|
|
14
|
+
# - `features`: union of GSUB + GPOS feature tags across every
|
|
15
|
+
# script (sorted, unique).
|
|
16
|
+
# - `by_script`: per-script breakdown preserving the
|
|
17
|
+
# "feature X is for script Y" relationship that the flat arrays
|
|
18
|
+
# discarded.
|
|
19
|
+
# - `has_gsub` / `has_gpos`: presence flags so consumers can tell
|
|
20
|
+
# "font has no layout" from "font has GSUB but no GPOS".
|
|
21
|
+
#
|
|
22
|
+
# nil for Type 1 fonts (no SFNT table structure).
|
|
23
|
+
class OpenTypeLayout < Lutaml::Model::Serializable
|
|
24
|
+
attribute :scripts, :string, collection: true
|
|
25
|
+
attribute :features, :string, collection: true
|
|
26
|
+
attribute :by_script, ScriptFeatures, collection: true
|
|
27
|
+
attribute :has_gsub, Lutaml::Model::Type::Boolean
|
|
28
|
+
attribute :has_gpos, Lutaml::Model::Type::Boolean
|
|
29
|
+
|
|
30
|
+
key_value do
|
|
31
|
+
map "scripts", to: :scripts
|
|
32
|
+
map "features", to: :features
|
|
33
|
+
map "by_script", to: :by_script
|
|
34
|
+
map "has_gsub", to: :has_gsub
|
|
35
|
+
map "has_gpos", to: :has_gpos
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Models
|
|
7
|
+
module Audit
|
|
8
|
+
# One row in a LibrarySummary's script-coverage matrix.
|
|
9
|
+
#
|
|
10
|
+
# Lists every face (by postscript_name) whose cmap covers at least
|
|
11
|
+
# one codepoint assigned to a Unicode script. Lets a librarian
|
|
12
|
+
# answer "which fonts cover Cyrillic?" without re-auditing.
|
|
13
|
+
class ScriptCoverageRow < Lutaml::Model::Serializable
|
|
14
|
+
attribute :script, :string
|
|
15
|
+
attribute :face_count, :integer
|
|
16
|
+
attribute :faces, :string, collection: true
|
|
17
|
+
|
|
18
|
+
key_value do
|
|
19
|
+
map "script", to: :script
|
|
20
|
+
map "face_count", to: :face_count
|
|
21
|
+
map "faces", to: :faces
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|