fontisan 0.2.17 → 0.2.23
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.yml +6 -0
- data/.rubocop_todo.yml +86 -86
- data/CHANGELOG.md +12 -2
- data/README.adoc +53 -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/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 +189 -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.rb +31 -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/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 +26 -0
- data/lib/fontisan/config/cldr.yml +22 -0
- data/lib/fontisan/config/conversion_matrix.yml +38 -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.rb +9 -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 +19 -0
- data/lib/fontisan/metrics_calculator.rb +0 -2
- data/lib/fontisan/models/all_scripts_features_info.rb +0 -1
- 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.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 +16 -0
- data/lib/fontisan/pipeline/transformation_pipeline.rb +0 -7
- data/lib/fontisan/pipeline/variation_resolver.rb +0 -7
- data/lib/fontisan/pipeline.rb +14 -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 +23 -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 +55 -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/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 +121 -196
- metadata +46 -2
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "conversion_strategy"
|
|
4
|
-
require_relative "table_copier"
|
|
5
|
-
require_relative "outline_converter"
|
|
6
|
-
require_relative "woff_writer"
|
|
7
|
-
require_relative "woff2_encoder"
|
|
8
|
-
require_relative "svg_generator"
|
|
9
|
-
require_relative "type1_converter"
|
|
10
3
|
require "yaml"
|
|
11
4
|
|
|
12
5
|
module Fontisan
|
|
@@ -17,18 +10,14 @@ module Fontisan
|
|
|
17
10
|
# primary entry point for all format conversion operations. It:
|
|
18
11
|
# - Selects appropriate conversion strategy based on source/target formats
|
|
19
12
|
# - Validates conversions against the conversion matrix
|
|
13
|
+
# - Validates user-supplied options against the selected strategy's schema
|
|
20
14
|
# - Delegates actual conversion to strategy implementations
|
|
21
15
|
# - Provides clean error messages for unsupported conversions
|
|
22
16
|
#
|
|
23
17
|
# The converter uses a strategy pattern with pluggable strategies for
|
|
24
|
-
# different conversion types
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# - Woff2Encoder: TTF/OTF → WOFF2 compression
|
|
28
|
-
# - SvgGenerator: TTF/OTF → SVG font generation
|
|
29
|
-
#
|
|
30
|
-
# Supported conversions are defined in the conversion matrix configuration
|
|
31
|
-
# file, making it easy to extend without modifying code.
|
|
18
|
+
# different conversion types. Each strategy declares its own options via
|
|
19
|
+
# the `ConversionStrategy.option` DSL; the converter enforces the
|
|
20
|
+
# format ↔ option mapping at runtime.
|
|
32
21
|
#
|
|
33
22
|
# @example Converting TTF to OTF
|
|
34
23
|
# converter = Fontisan::Converters::FormatConverter.new
|
|
@@ -41,6 +30,81 @@ module Fontisan
|
|
|
41
30
|
# tables = converter.convert(font, :ttf) # TTF to TTF
|
|
42
31
|
# FontWriter.write_to_file(tables, 'copy.ttf')
|
|
43
32
|
class FormatConverter
|
|
33
|
+
# Registry of all strategy classes. Single source of truth for option
|
|
34
|
+
# discovery and strategy lookup. Add a new format by appending its
|
|
35
|
+
# strategy class here.
|
|
36
|
+
STRATEGY_CLASSES = [
|
|
37
|
+
TableCopier,
|
|
38
|
+
OutlineConverter,
|
|
39
|
+
Type1Converter,
|
|
40
|
+
WoffWriter,
|
|
41
|
+
Woff2Encoder,
|
|
42
|
+
SvgGenerator,
|
|
43
|
+
].freeze
|
|
44
|
+
|
|
45
|
+
class << self
|
|
46
|
+
# All option names declared by any strategy. Used by ConversionOptions
|
|
47
|
+
# to keep its generating-options list in sync without duplicating the
|
|
48
|
+
# schema.
|
|
49
|
+
#
|
|
50
|
+
# @return [Array<Symbol>]
|
|
51
|
+
def all_strategy_option_names
|
|
52
|
+
STRATEGY_CLASSES.flat_map { |k| k.supported_options.map(&:name) }.uniq
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Look up the strategy class that handles a given conversion.
|
|
56
|
+
#
|
|
57
|
+
# @param source_format [Symbol]
|
|
58
|
+
# @param target_format [Symbol]
|
|
59
|
+
# @return [Class, nil]
|
|
60
|
+
def strategy_class_for(source_format, target_format)
|
|
61
|
+
STRATEGY_CLASSES.find do |klass|
|
|
62
|
+
klass.new.supports?(source_format, target_format)
|
|
63
|
+
rescue NotImplementedError
|
|
64
|
+
false
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Strategies whose supported_conversions include the given target.
|
|
69
|
+
#
|
|
70
|
+
# @param target_format [Symbol]
|
|
71
|
+
# @return [Array[Class]]
|
|
72
|
+
def strategies_for_target(target_format)
|
|
73
|
+
STRATEGY_CLASSES.select do |klass|
|
|
74
|
+
klass.new.supported_conversions.any? { |(_, t)| t == target_format }
|
|
75
|
+
rescue NotImplementedError
|
|
76
|
+
false
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Cross-format option check, independent of source format. Used by
|
|
81
|
+
# OutputWriter (which doesn't know the source format at write time)
|
|
82
|
+
# to catch e.g. `--brotli-quality` passed with `--to woff`.
|
|
83
|
+
#
|
|
84
|
+
# @param target_format [Symbol]
|
|
85
|
+
# @param options [Hash]
|
|
86
|
+
# @return [void]
|
|
87
|
+
# @raise [ArgumentError] if a user-supplied option is declared only by
|
|
88
|
+
# strategies that don't handle this target
|
|
89
|
+
def validate_options_for_target!(target_format, options)
|
|
90
|
+
handlers = strategies_for_target(target_format)
|
|
91
|
+
handler_names = handlers.flat_map do |k|
|
|
92
|
+
k.supported_options.map(&:name)
|
|
93
|
+
end.to_set
|
|
94
|
+
non_handler_names = (STRATEGY_CLASSES - handlers)
|
|
95
|
+
.flat_map { |k| k.supported_options.map(&:name) }
|
|
96
|
+
|
|
97
|
+
conflicts = options.keys.select do |k|
|
|
98
|
+
non_handler_names.include?(k.to_sym)
|
|
99
|
+
end
|
|
100
|
+
return if conflicts.empty?
|
|
101
|
+
|
|
102
|
+
accepted = handler_names.empty? ? "(none)" : handler_names.join(", ")
|
|
103
|
+
raise ArgumentError,
|
|
104
|
+
"Option(s) #{conflicts.map(&:inspect).join(', ')} do not apply " \
|
|
105
|
+
"to --to #{target_format}. Accepted for #{target_format}: #{accepted}"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
44
108
|
# @return [Hash] Conversion matrix loaded from config
|
|
45
109
|
attr_reader :conversion_matrix
|
|
46
110
|
|
|
@@ -52,15 +116,7 @@ module Fontisan
|
|
|
52
116
|
# @param conversion_matrix_path [String, nil] Path to conversion matrix
|
|
53
117
|
# config. If nil, uses default.
|
|
54
118
|
def initialize(conversion_matrix_path: nil)
|
|
55
|
-
@strategies =
|
|
56
|
-
TableCopier.new,
|
|
57
|
-
OutlineConverter.new,
|
|
58
|
-
Type1Converter.new,
|
|
59
|
-
WoffWriter.new,
|
|
60
|
-
Woff2Encoder.new,
|
|
61
|
-
SvgGenerator.new,
|
|
62
|
-
]
|
|
63
|
-
|
|
119
|
+
@strategies = self.class::STRATEGY_CLASSES.map(&:new)
|
|
64
120
|
load_conversion_matrix(conversion_matrix_path)
|
|
65
121
|
end
|
|
66
122
|
|
|
@@ -105,8 +161,20 @@ module Fontisan
|
|
|
105
161
|
end
|
|
106
162
|
|
|
107
163
|
strategy = select_strategy(source_format, target_format)
|
|
108
|
-
|
|
109
|
-
|
|
164
|
+
|
|
165
|
+
# Enforce format ↔ option mapping at the orchestrator level so each
|
|
166
|
+
# strategy only sees options it declared. Cross-format misuse
|
|
167
|
+
# (e.g., `--zlib-level` on a WOFF2 conversion) raises here with a
|
|
168
|
+
# clear message; the strategy itself never has to defensively
|
|
169
|
+
# ignore unknown keys.
|
|
170
|
+
validate_strategy_options!(strategy, source_format, target_format,
|
|
171
|
+
options)
|
|
172
|
+
sliced = slice_strategy_options(options, strategy)
|
|
173
|
+
|
|
174
|
+
tables = strategy.convert(
|
|
175
|
+
font,
|
|
176
|
+
sliced.merge(target_format: target_format),
|
|
177
|
+
)
|
|
110
178
|
|
|
111
179
|
# Preserve variation data if requested and font is variable
|
|
112
180
|
if options.fetch(:preserve_variation, true) && variable_font?(font)
|
|
@@ -178,8 +246,6 @@ module Fontisan
|
|
|
178
246
|
# @option options [Integer] :instance_index Named instance index
|
|
179
247
|
# @return [Hash] Hash with :svg_xml key
|
|
180
248
|
def convert_variable_to_svg(font, options = {})
|
|
181
|
-
require_relative "../variation/variable_svg_generator"
|
|
182
|
-
|
|
183
249
|
coordinates = options[:instance_coordinates] || {}
|
|
184
250
|
generator = Variation::VariableSvgGenerator.new(font, coordinates)
|
|
185
251
|
|
|
@@ -214,7 +280,6 @@ module Fontisan
|
|
|
214
280
|
options)
|
|
215
281
|
# Case 1: Compatible formats (same outline format) - just copy tables
|
|
216
282
|
if compatible_variation_formats?(source_format, target_format)
|
|
217
|
-
require_relative "../variation/variation_preserver"
|
|
218
283
|
Variation::VariationPreserver.preserve(font, tables, options)
|
|
219
284
|
|
|
220
285
|
# Case 2: Different outline formats - convert variation data
|
|
@@ -278,9 +343,6 @@ options)
|
|
|
278
343
|
# @return [Hash<String, String>] Tables with converted variation
|
|
279
344
|
def convert_variation_data(font, tables, source_format, target_format,
|
|
280
345
|
_options)
|
|
281
|
-
require_relative "../variation/variation_preserver"
|
|
282
|
-
require_relative "../variation/converter"
|
|
283
|
-
|
|
284
346
|
# For now, just preserve common tables and warn about conversion
|
|
285
347
|
warn "WARNING: Full variation conversion (#{source_format} → " \
|
|
286
348
|
"#{target_format}) not yet implemented. " \
|
|
@@ -420,6 +482,55 @@ _options)
|
|
|
420
482
|
"Cannot detect font format: missing both CFF and glyf tables"
|
|
421
483
|
end
|
|
422
484
|
end
|
|
485
|
+
|
|
486
|
+
# Cross-format option check. Any user-supplied key that belongs to
|
|
487
|
+
# another strategy (i.e., is valid for some other format) raises
|
|
488
|
+
# immediately — this is what makes `--zlib-level --to woff2` fail
|
|
489
|
+
# predictably.
|
|
490
|
+
#
|
|
491
|
+
# @param strategy [Object] Selected strategy instance
|
|
492
|
+
# @param source_format [Symbol]
|
|
493
|
+
# @param target_format [Symbol]
|
|
494
|
+
# @param options [Hash]
|
|
495
|
+
# @return [void]
|
|
496
|
+
# @raise [ArgumentError] if a user option is declared by another strategy
|
|
497
|
+
# @raise [ArgumentError] if a user option fails type/range validation
|
|
498
|
+
def validate_strategy_options!(strategy, source_format, target_format,
|
|
499
|
+
options)
|
|
500
|
+
this_class = strategy.class
|
|
501
|
+
other_classes = self.class::STRATEGY_CLASSES.reject do |k|
|
|
502
|
+
k == this_class
|
|
503
|
+
end
|
|
504
|
+
other_names = other_classes.flat_map do |k|
|
|
505
|
+
k.supported_options.map(&:name)
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
conflicts = options.keys.select do |k|
|
|
509
|
+
other_names.include?(k.to_sym)
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
if conflicts.any?
|
|
513
|
+
accepted = this_class.supported_options.map(&:name)
|
|
514
|
+
accepted_list = accepted.empty? ? "(none)" : accepted.join(", ")
|
|
515
|
+
raise ArgumentError,
|
|
516
|
+
"Option(s) #{conflicts.map(&:inspect).join(', ')} do not apply " \
|
|
517
|
+
"to #{source_format}→#{target_format} " \
|
|
518
|
+
"(#{this_class.name.demodulize}). " \
|
|
519
|
+
"Accepted: #{accepted_list}"
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
this_class.validate_options!(slice_strategy_options(options, strategy))
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Slice an options hash to only the keys declared by the given strategy.
|
|
526
|
+
#
|
|
527
|
+
# @param options [Hash]
|
|
528
|
+
# @param strategy [Object]
|
|
529
|
+
# @return [Hash]
|
|
530
|
+
def slice_strategy_options(options, strategy)
|
|
531
|
+
names = strategy.class.supported_options.to_set(&:name)
|
|
532
|
+
options.select { |k, _| names.include?(k.to_sym) }
|
|
533
|
+
end
|
|
423
534
|
end
|
|
424
535
|
end
|
|
425
536
|
end
|
|
@@ -1,24 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../conversion_options"
|
|
4
|
-
require_relative "conversion_strategy"
|
|
5
|
-
require_relative "../outline_extractor"
|
|
6
|
-
require_relative "../models/outline"
|
|
7
|
-
require_relative "../tables/cff/charstring_builder"
|
|
8
|
-
require_relative "outline_extraction"
|
|
9
|
-
require_relative "cff_table_builder"
|
|
10
|
-
require_relative "glyf_table_builder"
|
|
11
|
-
require_relative "outline_optimizer"
|
|
12
|
-
require_relative "../hints/truetype_hint_extractor"
|
|
13
|
-
require_relative "../hints/postscript_hint_extractor"
|
|
14
|
-
require_relative "../hints/hint_converter"
|
|
15
|
-
require_relative "../hints/truetype_hint_applier"
|
|
16
|
-
require_relative "../hints/postscript_hint_applier"
|
|
17
|
-
require_relative "../tables/cff2"
|
|
18
|
-
require_relative "../variation/data_extractor"
|
|
19
|
-
require_relative "../variation/instance_generator"
|
|
20
|
-
require_relative "../variation/converter"
|
|
21
|
-
|
|
22
3
|
module Fontisan
|
|
23
4
|
module Converters
|
|
24
5
|
# Strategy for converting between TTF and OTF outline formats
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../outline_extractor"
|
|
4
|
-
require_relative "../tables/cff/charstring_builder"
|
|
5
|
-
require_relative "../tables/glyf/glyph_builder"
|
|
6
|
-
require_relative "../tables/glyf/compound_glyph_resolver"
|
|
7
|
-
|
|
8
3
|
module Fontisan
|
|
9
4
|
module Converters
|
|
10
5
|
# Extracts all glyph outlines from a font for conversion purposes
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../optimizers/pattern_analyzer"
|
|
4
|
-
require_relative "../optimizers/subroutine_optimizer"
|
|
5
|
-
require_relative "../optimizers/subroutine_builder"
|
|
6
|
-
require_relative "../optimizers/charstring_rewriter"
|
|
7
|
-
|
|
8
3
|
module Fontisan
|
|
9
4
|
module Converters
|
|
10
5
|
# Optimizes CFF CharStrings using subroutine extraction
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../conversion_options"
|
|
4
|
-
require_relative "../type1/charstring_converter"
|
|
5
|
-
require_relative "../type1/cff_to_type1_converter"
|
|
6
|
-
require_relative "../type1/font_dictionary"
|
|
7
|
-
require_relative "../type1/charstrings"
|
|
8
|
-
require_relative "../type1/seac_expander"
|
|
9
|
-
require_relative "../type1_font"
|
|
10
|
-
require_relative "cff_table_builder"
|
|
11
|
-
|
|
12
3
|
module Fontisan
|
|
13
4
|
module Converters
|
|
14
5
|
# Converter for Adobe Type 1 fonts to/from SFNT formats.
|
|
@@ -1054,8 +1045,6 @@ module Fontisan
|
|
|
1054
1045
|
# @param font [Type1Font] Source Type 1 font
|
|
1055
1046
|
# @return [String] cmap table binary data
|
|
1056
1047
|
def build_cmap_table(font)
|
|
1057
|
-
require_relative "../type1/agl"
|
|
1058
|
-
|
|
1059
1048
|
data = (+"").b
|
|
1060
1049
|
|
|
1061
1050
|
# Get encoding from Type1Font
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "conversion_strategy"
|
|
4
|
-
require_relative "../woff2/header"
|
|
5
|
-
require_relative "../woff2/directory"
|
|
6
|
-
require_relative "../woff2/table_transformer"
|
|
7
|
-
require_relative "../utilities/brotli_wrapper"
|
|
8
|
-
require_relative "../utilities/checksum_calculator"
|
|
9
3
|
# Validation temporarily disabled - will be reimplemented with new DSL framework in Week 3+
|
|
10
4
|
# require_relative "../validation/woff2_validator"
|
|
11
5
|
require "yaml"
|
|
@@ -50,31 +44,45 @@ module Fontisan
|
|
|
50
44
|
# @return [Hash] Configuration settings
|
|
51
45
|
attr_reader :config
|
|
52
46
|
|
|
53
|
-
# Initialize encoder with configuration
|
|
47
|
+
# Initialize encoder with configuration.
|
|
48
|
+
#
|
|
49
|
+
# The encoder is stateless per call; all conversion knobs come through
|
|
50
|
+
# the per-convert options hash. The config file supplies default values
|
|
51
|
+
# only for fields not expressed via the DSL.
|
|
54
52
|
#
|
|
55
53
|
# @param config_path [String, nil] Path to config file
|
|
56
54
|
def initialize(config_path: nil)
|
|
57
55
|
@config = load_configuration(config_path)
|
|
58
56
|
end
|
|
59
57
|
|
|
60
|
-
|
|
58
|
+
option :brotli_quality, type: :integer, range: 0..11, default: 11,
|
|
59
|
+
cli: "--brotli-quality=N",
|
|
60
|
+
desc: "Brotli quality (0=fastest, 11=smallest)"
|
|
61
|
+
option :transform_tables, type: :boolean, default: false,
|
|
62
|
+
cli: "--[no-]transform-tables",
|
|
63
|
+
desc: "apply glyf/loca and hmtx transformations"
|
|
64
|
+
|
|
65
|
+
# Convert font to WOFF2 format.
|
|
61
66
|
#
|
|
62
|
-
# Returns a hash with
|
|
63
|
-
# This is different from other converters that return table data.
|
|
67
|
+
# Returns a hash with `:woff2_binary` containing the complete WOFF2 file.
|
|
64
68
|
#
|
|
65
69
|
# @param font [TrueTypeFont, OpenTypeFont] Source font
|
|
66
|
-
# @param options [Hash]
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
# @
|
|
71
|
-
# @
|
|
72
|
-
# @raise [Error]
|
|
70
|
+
# @param options [Hash{Symbol => Object}] Per-call options:
|
|
71
|
+
# - `:brotli_quality` (0–11, default from config or 11)
|
|
72
|
+
# - `:transform_tables` (bool, default false)
|
|
73
|
+
# - `:quality` — legacy alias for `:brotli_quality` (backward compat)
|
|
74
|
+
# @return [Hash{Symbol => String}] `{ woff2_binary: <bytes> }`
|
|
75
|
+
# @raise [ArgumentError] if any option fails validation
|
|
76
|
+
# @raise [Fontisan::Error] if encoding fails
|
|
73
77
|
def convert(font, options = {})
|
|
74
78
|
validate(font, :woff2)
|
|
75
79
|
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
resolved = normalize_legacy_quality(options)
|
|
81
|
+
self.class.validate_options!(strategy_options(resolved))
|
|
82
|
+
|
|
83
|
+
quality = resolved.fetch(:brotli_quality) do
|
|
84
|
+
config["brotli"]["quality"]
|
|
85
|
+
end
|
|
78
86
|
|
|
79
87
|
# Detect font flavor
|
|
80
88
|
flavor = detect_flavor(font)
|
|
@@ -84,7 +92,7 @@ module Fontisan
|
|
|
84
92
|
|
|
85
93
|
# Transform tables (if enabled)
|
|
86
94
|
transformer = Woff2::TableTransformer.new(font)
|
|
87
|
-
transform_enabled =
|
|
95
|
+
transform_enabled = resolved.fetch(:transform_tables, false)
|
|
88
96
|
|
|
89
97
|
# Build table directory entries
|
|
90
98
|
entries = build_table_entries(table_data, transformer,
|
|
@@ -201,6 +209,27 @@ module Fontisan
|
|
|
201
209
|
# Extend Woff2Font with in-memory loading
|
|
202
210
|
Woff2Font.singleton_class.prepend(Woff2FontMemoryLoader)
|
|
203
211
|
|
|
212
|
+
# Normalize legacy `:quality` option to `:brotli_quality`.
|
|
213
|
+
# Internal callers and older specs use `:quality`; the public DSL uses
|
|
214
|
+
# `:brotli_quality`. Returns a new hash; does not mutate the input.
|
|
215
|
+
#
|
|
216
|
+
# @param options [Hash]
|
|
217
|
+
# @return [Hash]
|
|
218
|
+
def normalize_legacy_quality(options)
|
|
219
|
+
return options unless options.key?(:quality) && !options.key?(:brotli_quality)
|
|
220
|
+
|
|
221
|
+
options.merge(brotli_quality: options[:quality])
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Slice options to those declared by this strategy.
|
|
225
|
+
#
|
|
226
|
+
# @param options [Hash]
|
|
227
|
+
# @return [Hash]
|
|
228
|
+
def strategy_options(options)
|
|
229
|
+
names = self.class.supported_options.to_set(&:name)
|
|
230
|
+
options.select { |k, _| names.include?(k.to_sym) }
|
|
231
|
+
end
|
|
232
|
+
|
|
204
233
|
# Load configuration from YAML file
|
|
205
234
|
#
|
|
206
235
|
# @param path [String, nil] Path to config file
|