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,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Fontisan
|
|
7
|
+
module Ucd
|
|
8
|
+
# Sorted, run-length-encoded lookup table over Unicode codepoints.
|
|
9
|
+
#
|
|
10
|
+
# One Index answers "what <thing> does codepoint N belong to?" for one
|
|
11
|
+
# property (block, or script). Lookup is O(log N) via bsearch.
|
|
12
|
+
class Index
|
|
13
|
+
include Enumerable
|
|
14
|
+
|
|
15
|
+
# @param entries [Array<RangeEntry>] sorted, disjoint
|
|
16
|
+
def initialize(entries)
|
|
17
|
+
@entries = entries.sort
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [Array<RangeEntry>]
|
|
21
|
+
attr_reader :entries
|
|
22
|
+
|
|
23
|
+
def each(&)
|
|
24
|
+
@entries.each(&)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def size
|
|
28
|
+
@entries.size
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param codepoint [Integer] Unicode codepoint
|
|
32
|
+
# @return [String, nil] the name of the range covering `codepoint`, or nil
|
|
33
|
+
def lookup(codepoint)
|
|
34
|
+
idx = bsearch_index(codepoint)
|
|
35
|
+
idx && @entries[idx].name
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Enumerate every range whose [first_cp, last_cp] overlaps the
|
|
39
|
+
# inclusive query range.
|
|
40
|
+
# @param first [Integer]
|
|
41
|
+
# @param last [Integer]
|
|
42
|
+
# @return [Enumerator<RangeEntry>]
|
|
43
|
+
def each_overlapping(first, last, &)
|
|
44
|
+
return enum_for(:each_overlapping, first, last) unless block_given?
|
|
45
|
+
|
|
46
|
+
start_idx = bsearch_first_overlap(first)
|
|
47
|
+
return if start_idx.nil?
|
|
48
|
+
|
|
49
|
+
@entries[start_idx..].each do |entry|
|
|
50
|
+
break if entry.first_cp > last
|
|
51
|
+
|
|
52
|
+
yield entry if entry.last_cp >= first
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Serialize to a YAML file.
|
|
57
|
+
# @param path [String, Pathname]
|
|
58
|
+
# @return [void]
|
|
59
|
+
def save(path)
|
|
60
|
+
File.open(path, "w") do |file|
|
|
61
|
+
YAML.dump(@entries.map(&:to_h), file)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Load from a YAML file previously written by #save.
|
|
66
|
+
# @param path [String, Pathname] (required)
|
|
67
|
+
# @return [Index]
|
|
68
|
+
def self.load(path)
|
|
69
|
+
hashes = YAML.load_file(path)
|
|
70
|
+
new(hashes.map { |h| RangeEntry.from_h(h) })
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Build an Index from raw [first_cp, last_cp, name] triples.
|
|
74
|
+
# @param triples [Array<Array(Integer, Integer, String)>]
|
|
75
|
+
# @return [Index]
|
|
76
|
+
def self.from_triples(triples)
|
|
77
|
+
new(triples.map { |first, last, name| RangeEntry.new(first, last, name) })
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# Binary search for the entry whose range contains `codepoint`.
|
|
83
|
+
# Returns the index in @entries, or nil.
|
|
84
|
+
def bsearch_index(codepoint)
|
|
85
|
+
@entries.bsearch_index do |entry|
|
|
86
|
+
if codepoint < entry.first_cp
|
|
87
|
+
-1
|
|
88
|
+
elsif codepoint > entry.last_cp
|
|
89
|
+
1
|
|
90
|
+
else
|
|
91
|
+
0
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Find the first entry whose last_cp >= `first`. Returns nil if no
|
|
97
|
+
# entry overlaps anything >= `first`.
|
|
98
|
+
def bsearch_first_overlap(first)
|
|
99
|
+
@entries.bsearch_index { |entry| entry.last_cp >= first }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ucd
|
|
5
|
+
# Turns a parsed Models::Ucd::Ucd instance into two compact
|
|
6
|
+
# run-length-encoded indices (blocks + scripts), and persists them to
|
|
7
|
+
# the cache for future Index loads.
|
|
8
|
+
#
|
|
9
|
+
# Index layout on disk (YAML):
|
|
10
|
+
#
|
|
11
|
+
# <root>/<version>/index/
|
|
12
|
+
# blocks.yml
|
|
13
|
+
# scripts.yml
|
|
14
|
+
#
|
|
15
|
+
# Each file is an array of `{ first_cp:, last_cp:, name: }` hashes,
|
|
16
|
+
# sorted by first_cp, disjoint.
|
|
17
|
+
module IndexBuilder
|
|
18
|
+
class << self
|
|
19
|
+
# Build + persist both indices for a cached version.
|
|
20
|
+
# @param version [String]
|
|
21
|
+
# @return [Array(Index, Index)] blocks_index, scripts_index
|
|
22
|
+
def build(version)
|
|
23
|
+
ucd = load_ucd(version)
|
|
24
|
+
blocks, scripts = build_from_ucd(ucd)
|
|
25
|
+
CacheManager.index_dir(version).mkpath
|
|
26
|
+
blocks.save(CacheManager.blocks_index_path(version))
|
|
27
|
+
scripts.save(CacheManager.scripts_index_path(version))
|
|
28
|
+
[blocks, scripts]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Pure: build both indices from an in-memory Ucd model.
|
|
32
|
+
# @param ucd [Models::Ucd::Ucd]
|
|
33
|
+
# @return [Array(Index, Index)]
|
|
34
|
+
def build_from_ucd(ucd)
|
|
35
|
+
blocks_runs = collect_runs(ucd, :block)
|
|
36
|
+
scripts_runs = collect_runs(ucd, :script)
|
|
37
|
+
[Index.new(to_entries(blocks_runs)), Index.new(to_entries(scripts_runs))]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def load_ucd(version)
|
|
43
|
+
path = CacheManager.ucdxml_path(version)
|
|
44
|
+
xml = File.read(path)
|
|
45
|
+
Models::Ucd::Ucd.from_xml(xml)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Walk all UcdChar entries, group by the given property
|
|
49
|
+
# (:block or :script), and accumulate codepoint ranges per name.
|
|
50
|
+
# Returns Hash<String, Array<[Integer, Integer]>>.
|
|
51
|
+
def collect_runs(ucd, property)
|
|
52
|
+
runs_by_name = Hash.new { |h, k| h[k] = [] }
|
|
53
|
+
|
|
54
|
+
ucd.chars.each do |char|
|
|
55
|
+
name = char.public_send(property)
|
|
56
|
+
next if name.nil? || name.empty?
|
|
57
|
+
|
|
58
|
+
ranges_for_char(char).each do |first, last|
|
|
59
|
+
runs_by_name[name] << [first, last]
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
runs_by_name.each_value { |runs| coalesce!(runs) }
|
|
64
|
+
runs_by_name
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Returns Array<[Integer, Integer]> — the codepoint range(s) this
|
|
68
|
+
# char covers.
|
|
69
|
+
def ranges_for_char(char)
|
|
70
|
+
if char.range?
|
|
71
|
+
[[char.first_cp.to_i(16), char.last_cp.to_i(16)]]
|
|
72
|
+
elsif char.cp
|
|
73
|
+
cp_int = char.cp.to_i(16)
|
|
74
|
+
[[cp_int, cp_int]]
|
|
75
|
+
else
|
|
76
|
+
[]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Sort + merge adjacent/overlapping ranges in place.
|
|
81
|
+
# Input: Array<[Integer, Integer]>, mutated.
|
|
82
|
+
def coalesce!(runs)
|
|
83
|
+
return if runs.empty?
|
|
84
|
+
|
|
85
|
+
runs.sort!
|
|
86
|
+
merged = [runs.first]
|
|
87
|
+
runs[1..].each do |first, last|
|
|
88
|
+
prev = merged.last
|
|
89
|
+
if first <= prev[1] + 1
|
|
90
|
+
prev[1] = [prev[1], last].max
|
|
91
|
+
else
|
|
92
|
+
merged << [first, last]
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
runs.replace(merged)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Flatten {name => [[first,last],...]} into Array<RangeEntry>.
|
|
99
|
+
def to_entries(runs_by_name)
|
|
100
|
+
runs_by_name.flat_map do |name, runs|
|
|
101
|
+
runs.map { |first, last| RangeEntry.new(first, last, name) }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ucd
|
|
5
|
+
# Value object representing one row in a run-length-encoded UCD index.
|
|
6
|
+
#
|
|
7
|
+
# Sorted by `first_cp`. Entries within a single Index are disjoint
|
|
8
|
+
# (no overlapping ranges).
|
|
9
|
+
class RangeEntry
|
|
10
|
+
include Comparable
|
|
11
|
+
|
|
12
|
+
attr_reader :first_cp, :last_cp, :name
|
|
13
|
+
|
|
14
|
+
def initialize(first_cp, last_cp, name)
|
|
15
|
+
@first_cp = first_cp
|
|
16
|
+
@last_cp = last_cp
|
|
17
|
+
@name = name
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def covers?(codepoint)
|
|
21
|
+
codepoint >= @first_cp && codepoint <= @last_cp
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def size
|
|
25
|
+
@last_cp - @first_cp + 1
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def <=>(other)
|
|
29
|
+
[@first_cp, @last_cp] <=> [other.first_cp, other.last_cp]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ==(other)
|
|
33
|
+
other.is_a?(RangeEntry) &&
|
|
34
|
+
@first_cp == other.first_cp &&
|
|
35
|
+
@last_cp == other.last_cp &&
|
|
36
|
+
@name == other.name
|
|
37
|
+
end
|
|
38
|
+
alias eql? ==
|
|
39
|
+
|
|
40
|
+
def hash
|
|
41
|
+
[@first_cp, @last_cp, @name].hash
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Compact YAML-friendly form.
|
|
45
|
+
def to_h
|
|
46
|
+
{ first_cp: @first_cp, last_cp: @last_cp, name: @name }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.from_h(hash)
|
|
50
|
+
new(hash[:first_cp] || hash["first_cp"],
|
|
51
|
+
hash[:last_cp] || hash["last_cp"],
|
|
52
|
+
hash[:name] || hash["name"])
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "rubygems"
|
|
6
|
+
|
|
7
|
+
module Fontisan
|
|
8
|
+
module Ucd
|
|
9
|
+
# Resolves a user-supplied version intent to a concrete version string.
|
|
10
|
+
#
|
|
11
|
+
# Three input modes:
|
|
12
|
+
#
|
|
13
|
+
# resolve(nil) # default_version from config
|
|
14
|
+
# resolve(:default) # default_version from config
|
|
15
|
+
# resolve("17.0.0") # explicit; validated against known_versions
|
|
16
|
+
# resolve(:latest) # probes listing_url, picks highest; falls
|
|
17
|
+
# # back to default on failure
|
|
18
|
+
#
|
|
19
|
+
module VersionResolver
|
|
20
|
+
class << self
|
|
21
|
+
# @param intent [nil, :default, :latest, String]
|
|
22
|
+
# @return [String] a concrete version string
|
|
23
|
+
def resolve(intent)
|
|
24
|
+
case intent
|
|
25
|
+
when nil, :default
|
|
26
|
+
Config.default_version
|
|
27
|
+
when :latest
|
|
28
|
+
probe_latest
|
|
29
|
+
else
|
|
30
|
+
validate!(intent)
|
|
31
|
+
intent
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Raise UnknownVersionError unless `version` is in known_versions.
|
|
36
|
+
# @param version [String]
|
|
37
|
+
# @return [void]
|
|
38
|
+
def validate!(version)
|
|
39
|
+
return if Config.known?(version)
|
|
40
|
+
|
|
41
|
+
raise UnknownVersionError,
|
|
42
|
+
"UCD version #{version.inspect} is not recognized. " \
|
|
43
|
+
"Known versions: #{Config.known_versions.join(', ')}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# Best-effort scrape of the unicode.org ucdxml directory listing.
|
|
49
|
+
# Returns the highest semver found, or Config.default_version on
|
|
50
|
+
# any failure.
|
|
51
|
+
def probe_latest
|
|
52
|
+
versions = fetch_directory_versions
|
|
53
|
+
return fallback_latest("directory listing was empty") if versions.empty?
|
|
54
|
+
|
|
55
|
+
highest = versions.max_by { |v| Gem::Version.new(v) }
|
|
56
|
+
if Config.known?(highest)
|
|
57
|
+
highest
|
|
58
|
+
else
|
|
59
|
+
fallback_latest("#{highest.inspect} is not in known_versions; using default")
|
|
60
|
+
end
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
fallback_latest(e.message)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def fallback_latest(reason)
|
|
66
|
+
warn "Ucd::VersionResolver: --latest probe failed (#{reason}); " \
|
|
67
|
+
"falling back to default #{Config.default_version.inspect}"
|
|
68
|
+
Config.default_version
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def fetch_directory_versions
|
|
72
|
+
uri = URI(Config.listing_url)
|
|
73
|
+
html = Net::HTTP.get(uri)
|
|
74
|
+
html.scan(%r{href="(\d+\.\d+\.\d+)/?"}i).flatten.uniq
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/fontisan/ucd.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Namespace file for UCD (Unicode Character Database) support.
|
|
4
|
+
#
|
|
5
|
+
# All Ucd::* constants are autoloaded from here. Top-level callers
|
|
6
|
+
# (`lib/fontisan.rb`) require this file; downstream files reference
|
|
7
|
+
# constants like Ucd::CacheManager and let autoload do the work.
|
|
8
|
+
|
|
9
|
+
module Fontisan
|
|
10
|
+
module Ucd
|
|
11
|
+
autoload :Config, "fontisan/ucd/config"
|
|
12
|
+
autoload :CacheManager, "fontisan/ucd/cache_manager"
|
|
13
|
+
autoload :Downloader, "fontisan/ucd/downloader"
|
|
14
|
+
autoload :VersionResolver, "fontisan/ucd/version_resolver"
|
|
15
|
+
autoload :IndexBuilder, "fontisan/ucd/index_builder"
|
|
16
|
+
autoload :Index, "fontisan/ucd/index"
|
|
17
|
+
autoload :Aggregator, "fontisan/ucd/aggregator"
|
|
18
|
+
autoload :RangeEntry, "fontisan/ucd/range_entry"
|
|
19
|
+
autoload :Error, "fontisan/ucd/error"
|
|
20
|
+
autoload :DownloadError, "fontisan/ucd/download_error"
|
|
21
|
+
autoload :UnknownVersionError, "fontisan/ucd/unknown_version_error"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Autoload hub for the Fontisan::Utilities namespace.
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Utilities
|
|
7
|
+
autoload :BrotliWrapper, "fontisan/utilities/brotli_wrapper"
|
|
8
|
+
autoload :ChecksumCalculator, "fontisan/utilities/checksum_calculator"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "basic_validator"
|
|
4
|
-
require_relative "font_book_validator"
|
|
5
|
-
require_relative "opentype_validator"
|
|
6
|
-
require_relative "web_font_validator"
|
|
7
|
-
|
|
8
3
|
module Fontisan
|
|
9
4
|
module Validators
|
|
10
5
|
# ProfileLoader manages validation profiles and loads appropriate validators
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Autoload hub for the Fontisan::Validators namespace.
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Validators
|
|
7
|
+
autoload :BasicValidator, "fontisan/validators/basic_validator"
|
|
8
|
+
autoload :FontBookValidator, "fontisan/validators/font_book_validator"
|
|
9
|
+
autoload :OpenTypeValidator, "fontisan/validators/opentype_validator"
|
|
10
|
+
autoload :ProfileLoader, "fontisan/validators/profile_loader"
|
|
11
|
+
autoload :Validator, "fontisan/validators/validator"
|
|
12
|
+
autoload :WebFontValidator, "fontisan/validators/web_font_validator"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Autoload hub for the Fontisan::Variable namespace.
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Variable
|
|
7
|
+
autoload :AxisNormalizer, "fontisan/variable/axis_normalizer"
|
|
8
|
+
autoload :DeltaApplicator, "fontisan/variable/delta_applicator"
|
|
9
|
+
autoload :GlyphDeltaProcessor, "fontisan/variable/glyph_delta_processor"
|
|
10
|
+
autoload :Instancer, "fontisan/variable/instancer"
|
|
11
|
+
autoload :MetricDeltaProcessor, "fontisan/variable/metric_delta_processor"
|
|
12
|
+
autoload :RegionMatcher, "fontisan/variable/region_matcher"
|
|
13
|
+
autoload :StaticFontBuilder, "fontisan/variable/static_font_builder"
|
|
14
|
+
autoload :TableUpdater, "fontisan/variable/table_updater"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "interpolator"
|
|
4
|
-
require_relative "region_matcher"
|
|
5
|
-
require_relative "metrics_adjuster"
|
|
6
|
-
require_relative "variation_context"
|
|
7
|
-
require_relative "table_accessor"
|
|
8
|
-
|
|
9
3
|
module Fontisan
|
|
10
4
|
module Variation
|
|
11
5
|
# Generates static font instances from variable fonts
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../font_writer"
|
|
4
|
-
require_relative "../converters/outline_converter"
|
|
5
|
-
require_relative "../converters/woff_writer"
|
|
6
|
-
require_relative "../error"
|
|
7
|
-
|
|
8
3
|
module Fontisan
|
|
9
4
|
module Variation
|
|
10
5
|
# Writes generated static font instances to files in various formats
|