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,10 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "base_command"
|
|
4
|
-
require_relative "../collection/builder"
|
|
5
|
-
require_relative "../collection/dfont_builder"
|
|
6
|
-
require_relative "../font_loader"
|
|
7
|
-
|
|
8
3
|
module Fontisan
|
|
9
4
|
module Commands
|
|
10
5
|
# Command for packing multiple fonts into a TTC/OTC collection
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "base_command"
|
|
4
|
-
require_relative "../font_loader"
|
|
5
|
-
require_relative "../font_writer"
|
|
6
3
|
require "fileutils"
|
|
7
4
|
|
|
8
5
|
module Fontisan
|
|
@@ -144,10 +141,8 @@ module Fontisan
|
|
|
144
141
|
# Load as TTC or OTC based on extension hint
|
|
145
142
|
# Both use same structure, main difference is expected font types
|
|
146
143
|
if ext == ".otc"
|
|
147
|
-
require_relative "../open_type_collection"
|
|
148
144
|
OpenTypeCollection.read(io)
|
|
149
145
|
else
|
|
150
|
-
require_relative "../true_type_collection"
|
|
151
146
|
TrueTypeCollection.read(io)
|
|
152
147
|
end
|
|
153
148
|
end
|
|
@@ -252,8 +247,6 @@ module Fontisan
|
|
|
252
247
|
# @param output_path [String] Output file path
|
|
253
248
|
# @return [void]
|
|
254
249
|
def convert_and_write(font, output_path)
|
|
255
|
-
require_relative "../converters/format_converter"
|
|
256
|
-
|
|
257
250
|
converter = Converters::FormatConverter.new
|
|
258
251
|
converter.convert(font, @format, output_path: output_path)
|
|
259
252
|
rescue StandardError => e
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "base_command"
|
|
4
|
-
require_relative "../validators/profile_loader"
|
|
5
|
-
require_relative "../font_loader"
|
|
6
|
-
require_relative "../tables/name"
|
|
7
|
-
|
|
8
3
|
module Fontisan
|
|
9
4
|
module Commands
|
|
10
5
|
# ValidateCommand provides CLI interface for font validation
|
|
@@ -138,9 +133,6 @@ module Fontisan
|
|
|
138
133
|
# @param mode [Symbol] Loading mode
|
|
139
134
|
# @return [Integer] Exit code
|
|
140
135
|
def validate_collection(mode)
|
|
141
|
-
require_relative "../models/collection_validation_report"
|
|
142
|
-
require_relative "../models/font_report"
|
|
143
|
-
|
|
144
136
|
# Load collection metadata
|
|
145
137
|
collection = FontLoader.load_collection(@input)
|
|
146
138
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Autoload hub for the Fontisan::Commands namespace.
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Commands
|
|
7
|
+
autoload :BaseCommand, "fontisan/commands/base_command"
|
|
8
|
+
autoload :ConvertCommand, "fontisan/commands/convert_command"
|
|
9
|
+
autoload :DumpTableCommand, "fontisan/commands/dump_table_command"
|
|
10
|
+
autoload :ExportCommand, "fontisan/commands/export_command"
|
|
11
|
+
autoload :FeaturesCommand, "fontisan/commands/features_command"
|
|
12
|
+
autoload :GlyphsCommand, "fontisan/commands/glyphs_command"
|
|
13
|
+
autoload :InfoCommand, "fontisan/commands/info_command"
|
|
14
|
+
autoload :InstanceCommand, "fontisan/commands/instance_command"
|
|
15
|
+
autoload :LsCommand, "fontisan/commands/ls_command"
|
|
16
|
+
autoload :OpticalSizeCommand, "fontisan/commands/optical_size_command"
|
|
17
|
+
autoload :PackCommand, "fontisan/commands/pack_command"
|
|
18
|
+
autoload :ScriptsCommand, "fontisan/commands/scripts_command"
|
|
19
|
+
autoload :SubsetCommand, "fontisan/commands/subset_command"
|
|
20
|
+
autoload :TablesCommand, "fontisan/commands/tables_command"
|
|
21
|
+
autoload :UnicodeCommand, "fontisan/commands/unicode_command"
|
|
22
|
+
autoload :UnpackCommand, "fontisan/commands/unpack_command"
|
|
23
|
+
autoload :ValidateCommand, "fontisan/commands/validate_command"
|
|
24
|
+
autoload :VariableCommand, "fontisan/commands/variable_command"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# UCD alternative: CLDR (Common Locale Data Repository) configuration.
|
|
2
|
+
#
|
|
3
|
+
# This file is the single source of truth for which CLDR version Fontisan
|
|
4
|
+
# uses by default, what versions are recognized, and where to fetch them.
|
|
5
|
+
#
|
|
6
|
+
# Update `default_version` and `known_versions` when a new CLDR release
|
|
7
|
+
# ships (typically 2× per year, alongside Unicode releases).
|
|
8
|
+
# Users override at runtime via `fontisan cldr download --version X.Y.Z`
|
|
9
|
+
# or `fontisan audit --cldr-version X.Y.Z`.
|
|
10
|
+
|
|
11
|
+
default_version: "46.0.0"
|
|
12
|
+
|
|
13
|
+
known_versions:
|
|
14
|
+
- "45.0.0"
|
|
15
|
+
- "46.0.0"
|
|
16
|
+
|
|
17
|
+
# CLDR JSON releases are published on GitHub.
|
|
18
|
+
# Full URL: <base_url>/<version>/cldr-<version>-json-full.zip
|
|
19
|
+
base_url: "https://github.com/unicode-org/cldr-json/releases/download"
|
|
20
|
+
|
|
21
|
+
# Releases listing URL for `--latest` probing. JSON API.
|
|
22
|
+
listing_url: "https://api.github.com/repos/unicode-org/cldr-json/releases"
|
|
@@ -174,6 +174,25 @@ conversions:
|
|
|
174
174
|
WOFF decompression with zlib. Preserves all font data and metadata.
|
|
175
175
|
Automatically detects font flavor from WOFF header.
|
|
176
176
|
|
|
177
|
+
- from: woff
|
|
178
|
+
to: woff2
|
|
179
|
+
strategy: woff_reader
|
|
180
|
+
description: "Transcode WOFF to WOFF2 (re-wrap with Brotli)"
|
|
181
|
+
status: implemented
|
|
182
|
+
notes: >
|
|
183
|
+
Web-format transcoding. The pipeline decodes WOFF (zlib) into its
|
|
184
|
+
underlying SFNT tables, then re-wraps them as WOFF2 with Brotli.
|
|
185
|
+
Useful for migrating legacy web fonts to the smaller WOFF2 format.
|
|
186
|
+
|
|
187
|
+
- from: woff
|
|
188
|
+
to: svg
|
|
189
|
+
strategy: svg_generator
|
|
190
|
+
description: "Generate SVG font from WOFF"
|
|
191
|
+
status: implemented
|
|
192
|
+
notes: >
|
|
193
|
+
Decodes WOFF (zlib) to SFNT tables, then renders glyph outlines as
|
|
194
|
+
SVG paths. Note: SVG fonts are deprecated in favor of WOFF2.
|
|
195
|
+
|
|
177
196
|
# Phase 2 conversions (Milestone 2.2) - SVG
|
|
178
197
|
- from: ttf
|
|
179
198
|
to: svg
|
|
@@ -254,6 +273,25 @@ conversions:
|
|
|
254
273
|
Same-format copy operation. Decompresses and re-compresses WOFF2 data
|
|
255
274
|
with Brotli, useful for normalizing compression or updating metadata.
|
|
256
275
|
|
|
276
|
+
- from: woff2
|
|
277
|
+
to: woff
|
|
278
|
+
strategy: woff2_decoder
|
|
279
|
+
description: "Transcode WOFF2 to WOFF (re-wrap with zlib)"
|
|
280
|
+
status: implemented
|
|
281
|
+
notes: >
|
|
282
|
+
Web-format transcoding. The pipeline decodes WOFF2 (Brotli) into its
|
|
283
|
+
underlying SFNT tables, then re-wraps them as WOFF with zlib. Useful
|
|
284
|
+
for legacy browser compatibility where WOFF2 is unavailable.
|
|
285
|
+
|
|
286
|
+
- from: woff2
|
|
287
|
+
to: svg
|
|
288
|
+
strategy: svg_generator
|
|
289
|
+
description: "Generate SVG font from WOFF2"
|
|
290
|
+
status: implemented
|
|
291
|
+
notes: >
|
|
292
|
+
Decodes WOFF2 (Brotli) to SFNT tables, then renders glyph outlines as
|
|
293
|
+
SVG paths. Note: SVG fonts are deprecated in favor of WOFF2.
|
|
294
|
+
|
|
257
295
|
# Collection format conversions (TTC/OTC/dfont)
|
|
258
296
|
# Same-format collection operations
|
|
259
297
|
- from: ttc
|
data/lib/fontisan/constants.rb
CHANGED
|
@@ -89,6 +89,9 @@ module Fontisan
|
|
|
89
89
|
# Fvar table tag identifier (Font Variations)
|
|
90
90
|
FVAR_TAG = "fvar"
|
|
91
91
|
|
|
92
|
+
# Avar table tag identifier (Axis Variation)
|
|
93
|
+
AVAR_TAG = "avar"
|
|
94
|
+
|
|
92
95
|
# Gvar table tag identifier (Glyph Variations for TrueType)
|
|
93
96
|
GVAR_TAG = "gvar"
|
|
94
97
|
|
|
@@ -129,6 +132,22 @@ module Fontisan
|
|
|
129
132
|
# Control Value Table (metrics used by TrueType hinting)
|
|
130
133
|
CVT_TAG = "cvt "
|
|
131
134
|
|
|
135
|
+
# Grid-fitting And Scan-conversion Procedure table (per-ppem hinting policy)
|
|
136
|
+
GASP_TAG = "gasp"
|
|
137
|
+
|
|
138
|
+
# Color font tables.
|
|
139
|
+
# COLR: COLR vector color glyph table (v0 or v1).
|
|
140
|
+
# CPAL: Color Palette table.
|
|
141
|
+
# SVG: SVG-in-OpenType color glyph table (tag is "SVG " — 4 bytes incl. trailing space).
|
|
142
|
+
# CBDT/CBLC: Color Bitmap Data / Location (paired — EBLC/EBDT equivalents for color).
|
|
143
|
+
# sbix: Apple bitmap glyph table.
|
|
144
|
+
COLR_TAG = "COLR"
|
|
145
|
+
CPAL_TAG = "CPAL"
|
|
146
|
+
SVG_TAG = "SVG "
|
|
147
|
+
CBDT_TAG = "CBDT"
|
|
148
|
+
CBLC_TAG = "CBLC"
|
|
149
|
+
SBIX_TAG = "sbix"
|
|
150
|
+
|
|
132
151
|
# Magic number used for font file checksum adjustment calculation.
|
|
133
152
|
# This constant is used in conjunction with the file checksum to compute
|
|
134
153
|
# the checksumAdjustment value stored in the 'head' table.
|
|
@@ -35,8 +35,12 @@ module Fontisan
|
|
|
35
35
|
preserve_encoding
|
|
36
36
|
].freeze
|
|
37
37
|
|
|
38
|
-
# Generating options (output processing)
|
|
39
|
-
|
|
38
|
+
# Generating options (output processing) declared statically by
|
|
39
|
+
# ConversionOptions itself. Format-specific compression knobs (zlib_level,
|
|
40
|
+
# brotli_quality, transform_tables, etc.) are declared by each strategy
|
|
41
|
+
# via the ConversionStrategy DSL and discovered via
|
|
42
|
+
# FormatConverter.all_strategy_option_names — see `.generating_options`.
|
|
43
|
+
STATIC_GENERATING_OPTIONS = %i[
|
|
40
44
|
write_pfm
|
|
41
45
|
write_afm
|
|
42
46
|
write_inf
|
|
@@ -47,19 +51,28 @@ module Fontisan
|
|
|
47
51
|
optimize_tables
|
|
48
52
|
reencode_first_256
|
|
49
53
|
encoding_vector
|
|
50
|
-
compression
|
|
51
|
-
transform_tables
|
|
52
54
|
preserve_metadata
|
|
53
55
|
strip_metadata
|
|
54
56
|
target_format
|
|
55
57
|
].freeze
|
|
56
58
|
|
|
59
|
+
# Full list of recognized generating options, computed lazily because
|
|
60
|
+
# converters `require_relative` this file, creating a load cycle that
|
|
61
|
+
# prevents resolving `FormatConverter` at class-definition time. The first
|
|
62
|
+
# call resolves all strategies (which are loaded before any conversion
|
|
63
|
+
# actually runs) and caches the result.
|
|
64
|
+
#
|
|
65
|
+
# @return [Array<Symbol>]
|
|
66
|
+
def self.generating_options
|
|
67
|
+
@generating_options ||= (
|
|
68
|
+
STATIC_GENERATING_OPTIONS +
|
|
69
|
+
Converters::FormatConverter.all_strategy_option_names
|
|
70
|
+
).uniq.freeze
|
|
71
|
+
end
|
|
72
|
+
|
|
57
73
|
# Valid hinting modes
|
|
58
74
|
HINTING_MODES = %w[preserve auto none full].freeze
|
|
59
75
|
|
|
60
|
-
# Valid compression modes
|
|
61
|
-
COMPRESSION_MODES = %w[zlib brotli none].freeze
|
|
62
|
-
|
|
63
76
|
attr_reader :from, :to, :opening, :generating
|
|
64
77
|
|
|
65
78
|
# Initialize conversion options
|
|
@@ -199,9 +212,9 @@ module Fontisan
|
|
|
199
212
|
# Validate generating options
|
|
200
213
|
def validate_generating_options!
|
|
201
214
|
@generating.each_key do |key|
|
|
202
|
-
unless
|
|
215
|
+
unless self.class.generating_options.include?(key)
|
|
203
216
|
raise ArgumentError, "Unknown generating option: #{key}. " \
|
|
204
|
-
"Available: #{
|
|
217
|
+
"Available: #{self.class.generating_options.join(', ')}"
|
|
205
218
|
end
|
|
206
219
|
end
|
|
207
220
|
|
|
@@ -214,15 +227,6 @@ module Fontisan
|
|
|
214
227
|
end
|
|
215
228
|
end
|
|
216
229
|
|
|
217
|
-
# Validate compression mode
|
|
218
|
-
if @generating[:compression]
|
|
219
|
-
comp = @generating[:compression].to_s
|
|
220
|
-
unless COMPRESSION_MODES.include?(comp)
|
|
221
|
-
raise ArgumentError, "Invalid compression: #{comp}. " \
|
|
222
|
-
"Available: #{COMPRESSION_MODES.join(', ')}"
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
|
|
226
230
|
# Validate target_format for collection conversions
|
|
227
231
|
if @generating[:target_format]
|
|
228
232
|
target = @generating[:target_format].to_s
|
|
@@ -364,9 +368,16 @@ module Fontisan
|
|
|
364
368
|
from: :otf,
|
|
365
369
|
to: :woff2,
|
|
366
370
|
opening: {},
|
|
367
|
-
generating: {
|
|
371
|
+
generating: { brotli_quality: 11, transform_tables: true,
|
|
368
372
|
optimize_tables: true, preserve_metadata: true },
|
|
369
373
|
},
|
|
374
|
+
legacy_web: {
|
|
375
|
+
from: :otf,
|
|
376
|
+
to: :woff,
|
|
377
|
+
opening: {},
|
|
378
|
+
generating: { zlib_level: 9, optimize_tables: true,
|
|
379
|
+
preserve_metadata: true },
|
|
380
|
+
},
|
|
370
381
|
archive_to_modern: {
|
|
371
382
|
from: :ttc,
|
|
372
383
|
to: :otf,
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../conversion_options"
|
|
4
|
-
require_relative "format_converter"
|
|
5
|
-
require_relative "../collection/builder"
|
|
6
|
-
require_relative "../collection/dfont_builder"
|
|
7
|
-
require_relative "../parsers/dfont_parser"
|
|
8
|
-
require_relative "../font_loader"
|
|
9
|
-
|
|
10
3
|
module Fontisan
|
|
11
4
|
module Converters
|
|
12
5
|
# CollectionConverter handles conversion between collection formats
|
|
@@ -343,7 +336,6 @@ conv_options = nil)
|
|
|
343
336
|
# @return [Font] Font object
|
|
344
337
|
def build_font_from_tables(tables, format)
|
|
345
338
|
# Create temporary font from tables
|
|
346
|
-
require_relative "../font_writer"
|
|
347
339
|
require "stringio"
|
|
348
340
|
|
|
349
341
|
sfnt_version = format == :otf ? 0x4F54544F : 0x00010000
|
|
@@ -2,45 +2,171 @@
|
|
|
2
2
|
|
|
3
3
|
module Fontisan
|
|
4
4
|
module Converters
|
|
5
|
-
# Interface module
|
|
5
|
+
# Interface module and declarative options DSL for conversion strategies
|
|
6
6
|
#
|
|
7
7
|
# [`ConversionStrategy`](lib/fontisan/converters/conversion_strategy.rb)
|
|
8
|
-
# defines the contract that all conversion strategy classes must implement
|
|
9
|
-
#
|
|
10
|
-
# different conversion types (TTF→OTF, OTF→TTF, same-format copying).
|
|
8
|
+
# defines the contract that all conversion strategy classes must implement,
|
|
9
|
+
# plus a declarative DSL for declaring the options each strategy accepts.
|
|
11
10
|
#
|
|
12
|
-
#
|
|
13
|
-
# - convert(font, options) - Perform the actual conversion
|
|
14
|
-
# - supported_conversions - Return array of [source, target] format pairs
|
|
15
|
-
# - validate(font, target_format) - Validate conversion is possible
|
|
11
|
+
# ## Why a declarative options DSL
|
|
16
12
|
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
13
|
+
# Each format has its own spec-mandated knobs (WOFF: zlib level,
|
|
14
|
+
# WOFF2: Brotli quality, etc.). Letting each strategy declare its own
|
|
15
|
+
# options keeps the schema with the code that consumes it (encapsulation),
|
|
16
|
+
# and makes adding a new format a pure additive change (OCP): write a new
|
|
17
|
+
# strategy class, declare its options, done — no edits to central option
|
|
18
|
+
# lists, the CLI option parser, or ConversionOptions.
|
|
19
19
|
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
20
|
+
# The strategy is also the sole validator of its own options. The
|
|
21
|
+
# runtime check at `FormatConverter#convert` calls
|
|
22
|
+
# `strategy.class.validate_options!` to enforce the format ↔ option
|
|
23
|
+
# mapping (e.g., rejecting `--zlib-level` on a WOFF2 conversion). This
|
|
24
|
+
# is the MECE guarantee: every option belongs to exactly one strategy,
|
|
25
|
+
# and a strategy rejects anything it did not declare.
|
|
23
26
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# tables
|
|
28
|
-
# end
|
|
27
|
+
# @example Declaring options
|
|
28
|
+
# class WoffWriter
|
|
29
|
+
# include ConversionStrategy
|
|
29
30
|
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
31
|
+
# option :zlib_level, type: :integer, range: 0..9, default: 6,
|
|
32
|
+
# cli: "--zlib-level", desc: "zlib compression level"
|
|
33
|
+
# option :uncompressed, type: :boolean, default: false,
|
|
34
|
+
# cli: "--uncompressed", desc: "store tables uncompressed"
|
|
33
35
|
#
|
|
34
|
-
# def
|
|
35
|
-
#
|
|
36
|
-
#
|
|
36
|
+
# def convert(font, options = {})
|
|
37
|
+
# self.class.validate_options!(options)
|
|
38
|
+
# # ...
|
|
37
39
|
# end
|
|
38
40
|
# end
|
|
39
41
|
module ConversionStrategy
|
|
40
|
-
#
|
|
42
|
+
# Declarative description of a single strategy option.
|
|
43
|
+
# `allowed_values` is spelled out (not `values`) to avoid shadowing
|
|
44
|
+
# Struct#values.
|
|
45
|
+
Option = Struct.new(:name, :type, :default, :cli, :desc, :range,
|
|
46
|
+
:allowed_values, keyword_init: true)
|
|
47
|
+
|
|
48
|
+
# Class methods mixed into including classes via `included`.
|
|
49
|
+
module ClassMethods
|
|
50
|
+
# Declare an option this strategy accepts.
|
|
51
|
+
#
|
|
52
|
+
# @param name [Symbol] Option name (the hash key used in `convert`)
|
|
53
|
+
# @param type [Symbol] One of :integer, :boolean, :string
|
|
54
|
+
# @param default [Object] Default value when the caller omits the option
|
|
55
|
+
# @param cli [String] CLI flag shape (for help text generation)
|
|
56
|
+
# @param desc [String] Human-readable description
|
|
57
|
+
# @param range [Range, nil] For :integer; valid range
|
|
58
|
+
# @param values [Array, nil] For :string; allowed values
|
|
59
|
+
# @return [void]
|
|
60
|
+
def option(name, type:, default:, cli:, desc:, range: nil, values: nil)
|
|
61
|
+
declared_options << Option.new(
|
|
62
|
+
name: name, type: type, default: default, cli: cli, desc: desc,
|
|
63
|
+
range: range, allowed_values: values
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# All options declared by this class.
|
|
68
|
+
#
|
|
69
|
+
# @return [Array<Option>]
|
|
70
|
+
def declared_options
|
|
71
|
+
@declared_options ||= []
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Public accessor: the full list of options for this strategy.
|
|
75
|
+
alias supported_options declared_options
|
|
76
|
+
|
|
77
|
+
# Find the option schema for a given key (symbol or string).
|
|
78
|
+
#
|
|
79
|
+
# @param name [Symbol, String]
|
|
80
|
+
# @return [Option, nil]
|
|
81
|
+
def option_for(name)
|
|
82
|
+
supported_options.find { |o| o.name == name.to_sym }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Default values for every declared option.
|
|
86
|
+
#
|
|
87
|
+
# @return [Hash{Symbol => Object}]
|
|
88
|
+
def default_options
|
|
89
|
+
supported_options.to_h { |o| [o.name, o.default] }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Validate a user-provided options hash against this strategy's schema.
|
|
93
|
+
#
|
|
94
|
+
# Raises ArgumentError for any unknown key, wrong type, or
|
|
95
|
+
# out-of-range value. Called by FormatConverter after strategy
|
|
96
|
+
# selection, so cross-format misuse (e.g., `--zlib-level` on a
|
|
97
|
+
# WOFF2 conversion) is caught here.
|
|
98
|
+
#
|
|
99
|
+
# @param user_options [Hash{Symbol, String => Object}]
|
|
100
|
+
# @return [void]
|
|
101
|
+
# @raise [ArgumentError] if any key is unknown or any value is invalid
|
|
102
|
+
def validate_options!(user_options)
|
|
103
|
+
user_options.each_key do |key|
|
|
104
|
+
opt = option_for(key)
|
|
105
|
+
next if opt
|
|
106
|
+
|
|
107
|
+
names = supported_options.map(&:name)
|
|
108
|
+
list = names.empty? ? "(none)" : names.join(", ")
|
|
109
|
+
raise ArgumentError,
|
|
110
|
+
"Unknown option #{key.inspect} for #{name}. " \
|
|
111
|
+
"Supported: #{list}"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
user_options.each do |key, value|
|
|
115
|
+
opt = option_for(key)
|
|
116
|
+
validate_option_value!(opt, value) unless value.nil?
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
# Type-check a single value against its declared schema.
|
|
123
|
+
#
|
|
124
|
+
# @param opt [Option]
|
|
125
|
+
# @param value [Object]
|
|
126
|
+
# @return [void]
|
|
127
|
+
# @raise [ArgumentError] if value fails type or range/values check
|
|
128
|
+
def validate_option_value!(opt, value)
|
|
129
|
+
case opt.type
|
|
130
|
+
when :integer
|
|
131
|
+
unless value.is_a?(Integer)
|
|
132
|
+
raise ArgumentError,
|
|
133
|
+
"#{opt.name} must be an Integer, got #{value.inspect} " \
|
|
134
|
+
"(#{value.class})"
|
|
135
|
+
end
|
|
136
|
+
return unless opt.range && !opt.range.cover?(value)
|
|
137
|
+
|
|
138
|
+
raise ArgumentError,
|
|
139
|
+
"#{opt.name} must be in #{opt.range}, got #{value}"
|
|
140
|
+
when :boolean
|
|
141
|
+
return if [true, false].include?(value)
|
|
142
|
+
|
|
143
|
+
raise ArgumentError,
|
|
144
|
+
"#{opt.name} must be true or false, got #{value.inspect}"
|
|
145
|
+
when :string
|
|
146
|
+
unless value.is_a?(String)
|
|
147
|
+
raise ArgumentError,
|
|
148
|
+
"#{opt.name} must be a String, got #{value.class}"
|
|
149
|
+
end
|
|
150
|
+
return unless opt.allowed_values && !opt.allowed_values.include?(value)
|
|
151
|
+
|
|
152
|
+
raise ArgumentError,
|
|
153
|
+
"#{opt.name} must be one of #{opt.allowed_values.join(', ')}, " \
|
|
154
|
+
"got #{value.inspect}"
|
|
155
|
+
else
|
|
156
|
+
raise "Unknown option type #{opt.type.inspect} on #{opt.name}"
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Mix ClassMethods into any class that includes this module.
|
|
162
|
+
def self.included(base)
|
|
163
|
+
base.extend(ClassMethods)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Convert font to target format.
|
|
41
167
|
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
168
|
+
# Strategies must implement this. Subclasses should call
|
|
169
|
+
# `self.class.validate_options!(options)` first to enforce their schema.
|
|
44
170
|
#
|
|
45
171
|
# @param font [TrueTypeFont, OpenTypeFont] Source font
|
|
46
172
|
# @param options [Hash] Conversion options
|
|
@@ -51,30 +177,19 @@ module Fontisan
|
|
|
51
177
|
"#{self.class.name} must implement convert(font, options)"
|
|
52
178
|
end
|
|
53
179
|
|
|
54
|
-
# Get list of supported conversions
|
|
55
|
-
#
|
|
56
|
-
# Returns an array of [source_format, target_format] pairs that
|
|
57
|
-
# this strategy can handle.
|
|
180
|
+
# Get list of supported conversions.
|
|
58
181
|
#
|
|
59
|
-
# @return [Array<Array<Symbol>>] Supported
|
|
182
|
+
# @return [Array<Array<Symbol>>] Supported [source, target] pairs
|
|
60
183
|
# @raise [NotImplementedError] If not implemented by strategy
|
|
61
|
-
#
|
|
62
|
-
# @example
|
|
63
|
-
# strategy.supported_conversions
|
|
64
|
-
# # => [[:ttf, :otf], [:otf, :ttf]]
|
|
65
184
|
def supported_conversions
|
|
66
185
|
raise NotImplementedError,
|
|
67
186
|
"#{self.class.name} must implement supported_conversions"
|
|
68
187
|
end
|
|
69
188
|
|
|
70
|
-
# Validate that conversion is possible
|
|
71
|
-
#
|
|
72
|
-
# Checks if the given font can be converted to the target format.
|
|
73
|
-
# Should raise an error with a clear message if conversion is not
|
|
74
|
-
# possible.
|
|
189
|
+
# Validate that conversion is possible.
|
|
75
190
|
#
|
|
76
191
|
# @param font [TrueTypeFont, OpenTypeFont] Font to validate
|
|
77
|
-
# @param target_format [Symbol] Target format
|
|
192
|
+
# @param target_format [Symbol] Target format
|
|
78
193
|
# @return [Boolean] True if valid
|
|
79
194
|
# @raise [Error] If conversion is not possible
|
|
80
195
|
# @raise [NotImplementedError] If not implemented by strategy
|
|
@@ -83,11 +198,11 @@ module Fontisan
|
|
|
83
198
|
"#{self.class.name} must implement validate(font, target_format)"
|
|
84
199
|
end
|
|
85
200
|
|
|
86
|
-
# Check if strategy supports a conversion
|
|
201
|
+
# Check if strategy supports a given conversion.
|
|
87
202
|
#
|
|
88
|
-
# @param source_format [Symbol]
|
|
89
|
-
# @param target_format [Symbol]
|
|
90
|
-
# @return [Boolean]
|
|
203
|
+
# @param source_format [Symbol]
|
|
204
|
+
# @param target_format [Symbol]
|
|
205
|
+
# @return [Boolean]
|
|
91
206
|
def supports?(source_format, target_format)
|
|
92
207
|
supported_conversions.include?([source_format, target_format])
|
|
93
208
|
end
|