fontisan 0.2.17 → 0.2.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +14 -90
- data/README.adoc +257 -1
- data/docs/.vitepress/config.ts +68 -8
- data/docs/.vitepress/theme/style.css +570 -272
- data/docs/CONVERSION_GUIDE.adoc +31 -8
- data/docs/EXTRACT_TTC_MIGRATION.md +1 -1
- data/docs/WOFF_WOFF2_FORMATS.adoc +53 -0
- data/docs/api/conversion-options.md +37 -14
- data/docs/cli/audit.md +337 -0
- data/docs/cli/convert.md +20 -1
- data/docs/cli/index.md +31 -0
- data/docs/guide/color.md +1 -1
- data/docs/guide/conversion/options.md +32 -3
- data/docs/guide/conversion/ttf-otf.md +1 -1
- data/docs/guide/conversion/type1.md +1 -1
- data/docs/guide/conversion/web.md +91 -32
- data/docs/guide/conversion.md +6 -5
- data/docs/guide/formats/woff.md +35 -11
- data/docs/guide/index.md +2 -2
- data/docs/guide/migrations/extract-ttc.md +1 -1
- data/docs/guide/quick-start.md +4 -4
- data/docs/guide/type1.md +4 -4
- data/docs/guide/woff.md +19 -17
- data/docs/index.md +2 -0
- data/docs/lychee.toml +5 -1
- data/docs/package.json +1 -1
- data/docs/public/robots.txt +4 -0
- data/docs/scripts/post-build.mjs +81 -0
- data/lib/fontisan/audit/codepoint_range_coalescer.rb +41 -0
- data/lib/fontisan/audit/context.rb +122 -0
- data/lib/fontisan/audit/differ.rb +124 -0
- data/lib/fontisan/audit/extractors/aggregations.rb +54 -0
- data/lib/fontisan/audit/extractors/base.rb +26 -0
- data/lib/fontisan/audit/extractors/color_capabilities.rb +141 -0
- data/lib/fontisan/audit/extractors/coverage.rb +48 -0
- data/lib/fontisan/audit/extractors/hinting.rb +197 -0
- data/lib/fontisan/audit/extractors/identity.rb +52 -0
- data/lib/fontisan/audit/extractors/language_coverage.rb +37 -0
- data/lib/fontisan/audit/extractors/licensing.rb +79 -0
- data/lib/fontisan/audit/extractors/metrics.rb +103 -0
- data/lib/fontisan/audit/extractors/opentype_layout.rb +69 -0
- data/lib/fontisan/audit/extractors/provenance.rb +29 -0
- data/lib/fontisan/audit/extractors/style.rb +32 -0
- data/lib/fontisan/audit/extractors/variation_detail.rb +99 -0
- data/lib/fontisan/audit/extractors.rb +27 -0
- data/lib/fontisan/audit/library_aggregator.rb +83 -0
- data/lib/fontisan/audit/library_auditor.rb +90 -0
- data/lib/fontisan/audit/registry.rb +60 -0
- data/lib/fontisan/audit/style_extractor.rb +80 -0
- data/lib/fontisan/audit.rb +20 -0
- data/lib/fontisan/base_collection.rb +23 -9
- data/lib/fontisan/binary/structures.rb +0 -2
- data/lib/fontisan/binary.rb +11 -0
- data/lib/fontisan/cldr/aggregator.rb +33 -0
- data/lib/fontisan/cldr/cache_manager.rb +110 -0
- data/lib/fontisan/cldr/config.rb +59 -0
- data/lib/fontisan/cldr/download_error.rb +9 -0
- data/lib/fontisan/cldr/downloader.rb +79 -0
- data/lib/fontisan/cldr/error.rb +8 -0
- data/lib/fontisan/cldr/index.rb +64 -0
- data/lib/fontisan/cldr/index_builder.rb +72 -0
- data/lib/fontisan/cldr/unicode_set_parser.rb +172 -0
- data/lib/fontisan/cldr/unknown_version_error.rb +9 -0
- data/lib/fontisan/cldr/version_resolver.rb +91 -0
- data/lib/fontisan/cldr.rb +23 -0
- data/lib/fontisan/cli/cldr_cli.rb +85 -0
- data/lib/fontisan/cli/ucd_cli.rb +97 -0
- data/lib/fontisan/cli.rb +201 -2
- data/lib/fontisan/collection/builder.rb +0 -4
- data/lib/fontisan/collection/dfont_builder.rb +0 -4
- data/lib/fontisan/collection/shared_logic.rb +0 -2
- data/lib/fontisan/collection/writer.rb +0 -3
- data/lib/fontisan/collection.rb +15 -0
- data/lib/fontisan/commands/audit_command.rb +123 -0
- data/lib/fontisan/commands/audit_compare_command.rb +66 -0
- data/lib/fontisan/commands/audit_library_command.rb +46 -0
- data/lib/fontisan/commands/base_command.rb +0 -3
- data/lib/fontisan/commands/convert_command.rb +25 -20
- data/lib/fontisan/commands/dump_table_command.rb +0 -3
- data/lib/fontisan/commands/export_command.rb +0 -4
- data/lib/fontisan/commands/features_command.rb +0 -3
- data/lib/fontisan/commands/instance_command.rb +0 -5
- data/lib/fontisan/commands/ls_command.rb +0 -6
- data/lib/fontisan/commands/optical_size_command.rb +0 -3
- data/lib/fontisan/commands/pack_command.rb +0 -5
- data/lib/fontisan/commands/scripts_command.rb +0 -2
- data/lib/fontisan/commands/subset_command.rb +0 -3
- data/lib/fontisan/commands/unicode_command.rb +0 -3
- data/lib/fontisan/commands/unpack_command.rb +0 -7
- data/lib/fontisan/commands/validate_command.rb +0 -8
- data/lib/fontisan/commands/variable_command.rb +0 -3
- data/lib/fontisan/commands.rb +29 -0
- data/lib/fontisan/config/cldr.yml +22 -0
- data/lib/fontisan/config/conversion_matrix.yml +38 -0
- data/lib/fontisan/config/ucd.yml +23 -0
- data/lib/fontisan/constants.rb +19 -0
- data/lib/fontisan/conversion_options.rb +30 -19
- data/lib/fontisan/converters/cff_table_builder.rb +0 -3
- data/lib/fontisan/converters/collection_converter.rb +0 -8
- data/lib/fontisan/converters/conversion_strategy.rb +161 -46
- data/lib/fontisan/converters/format_converter.rb +143 -32
- data/lib/fontisan/converters/glyf_table_builder.rb +0 -2
- data/lib/fontisan/converters/outline_converter.rb +0 -19
- data/lib/fontisan/converters/outline_extraction.rb +0 -5
- data/lib/fontisan/converters/outline_optimizer.rb +0 -5
- data/lib/fontisan/converters/svg_generator.rb +0 -4
- data/lib/fontisan/converters/table_copier.rb +0 -2
- data/lib/fontisan/converters/type1_converter.rb +0 -11
- data/lib/fontisan/converters/woff2_encoder.rb +49 -20
- data/lib/fontisan/converters/woff_writer.rb +211 -282
- data/lib/fontisan/converters.rb +21 -0
- data/lib/fontisan/dfont_collection.rb +29 -10
- data/lib/fontisan/export/exporter.rb +0 -6
- data/lib/fontisan/export/transformers/font_to_ttx.rb +0 -9
- data/lib/fontisan/export/transformers/head_transformer.rb +0 -2
- data/lib/fontisan/export/transformers/hhea_transformer.rb +0 -2
- data/lib/fontisan/export/transformers/maxp_transformer.rb +0 -2
- data/lib/fontisan/export/transformers/name_transformer.rb +0 -2
- data/lib/fontisan/export/transformers/os2_transformer.rb +0 -2
- data/lib/fontisan/export/transformers/post_transformer.rb +0 -2
- data/lib/fontisan/export/transformers.rb +17 -0
- data/lib/fontisan/export.rb +13 -0
- data/lib/fontisan/font_loader.rb +14 -19
- data/lib/fontisan/font_writer.rb +0 -1
- data/lib/fontisan/formatters/audit_diff_text_renderer.rb +122 -0
- data/lib/fontisan/formatters/audit_text_renderer.rb +324 -0
- data/lib/fontisan/formatters/library_summary_text_renderer.rb +99 -0
- data/lib/fontisan/formatters/text_formatter.rb +6 -0
- data/lib/fontisan/formatters.rb +12 -0
- data/lib/fontisan/hints/hint_converter.rb +0 -1
- data/lib/fontisan/hints/postscript_hint_applier.rb +0 -9
- data/lib/fontisan/hints/postscript_hint_extractor.rb +0 -2
- data/lib/fontisan/hints/truetype_hint_extractor.rb +0 -2
- data/lib/fontisan/hints.rb +16 -0
- data/lib/fontisan/metrics_calculator.rb +0 -2
- data/lib/fontisan/models/all_scripts_features_info.rb +0 -1
- data/lib/fontisan/models/audit/audit_axis.rb +30 -0
- data/lib/fontisan/models/audit/audit_block.rb +32 -0
- data/lib/fontisan/models/audit/audit_diff.rb +77 -0
- data/lib/fontisan/models/audit/audit_report.rb +153 -0
- data/lib/fontisan/models/audit/codepoint_range.rb +40 -0
- data/lib/fontisan/models/audit/codepoint_set_diff.rb +34 -0
- data/lib/fontisan/models/audit/color_capabilities.rb +93 -0
- data/lib/fontisan/models/audit/duplicate_group.rb +23 -0
- data/lib/fontisan/models/audit/embedding_type.rb +76 -0
- data/lib/fontisan/models/audit/field_change.rb +28 -0
- data/lib/fontisan/models/audit/fs_selection_flags.rb +61 -0
- data/lib/fontisan/models/audit/gasp_range.rb +63 -0
- data/lib/fontisan/models/audit/hinting.rb +93 -0
- data/lib/fontisan/models/audit/library_summary.rb +40 -0
- data/lib/fontisan/models/audit/licensing.rb +48 -0
- data/lib/fontisan/models/audit/metrics.rb +111 -0
- data/lib/fontisan/models/audit/named_instance.rb +41 -0
- data/lib/fontisan/models/audit/opentype_layout.rb +40 -0
- data/lib/fontisan/models/audit/script_coverage_row.rb +26 -0
- data/lib/fontisan/models/audit/script_features.rb +28 -0
- data/lib/fontisan/models/audit/variation_detail.rb +44 -0
- data/lib/fontisan/models/audit.rb +33 -0
- data/lib/fontisan/models/cldr/language_coverage.rb +31 -0
- data/lib/fontisan/models/cldr.rb +12 -0
- data/lib/fontisan/models/collection_brief_info.rb +0 -1
- data/lib/fontisan/models/collection_info.rb +0 -2
- data/lib/fontisan/models/collection_list_info.rb +0 -1
- data/lib/fontisan/models/collection_validation_report.rb +0 -2
- data/lib/fontisan/models/color_glyph.rb +0 -1
- data/lib/fontisan/models/font_report.rb +0 -1
- data/lib/fontisan/models/ttx/tables.rb +21 -0
- data/lib/fontisan/models/ttx/ttfont.rb +0 -8
- data/lib/fontisan/models/ttx.rb +14 -0
- data/lib/fontisan/models/ucd/ucd.rb +38 -0
- data/lib/fontisan/models/ucd/ucd_char.rb +67 -0
- data/lib/fontisan/models/ucd.rb +19 -0
- data/lib/fontisan/models.rb +47 -0
- data/lib/fontisan/open_type_collection.rb +6 -5
- data/lib/fontisan/open_type_font.rb +8 -2
- data/lib/fontisan/open_type_font_extensions.rb +9 -9
- data/lib/fontisan/optimizers/pattern_analyzer.rb +0 -1
- data/lib/fontisan/optimizers.rb +14 -0
- data/lib/fontisan/outline_extractor.rb +0 -2
- data/lib/fontisan/parsers/dfont_parser.rb +0 -1
- data/lib/fontisan/parsers.rb +10 -0
- data/lib/fontisan/pipeline/format_detector.rb +29 -102
- data/lib/fontisan/pipeline/output_writer.rb +11 -9
- data/lib/fontisan/pipeline/strategies/instance_strategy.rb +0 -4
- data/lib/fontisan/pipeline/strategies/named_strategy.rb +0 -4
- data/lib/fontisan/pipeline/strategies/preserve_strategy.rb +0 -2
- data/lib/fontisan/pipeline/strategies.rb +14 -0
- data/lib/fontisan/pipeline/transformation_pipeline.rb +0 -7
- data/lib/fontisan/pipeline/variation_resolver.rb +0 -7
- data/lib/fontisan/pipeline.rb +13 -0
- data/lib/fontisan/sfnt_font.rb +29 -14
- data/lib/fontisan/sfnt_table.rb +0 -4
- data/lib/fontisan/subset/builder.rb +0 -6
- data/lib/fontisan/subset.rb +13 -0
- data/lib/fontisan/svg/font_generator.rb +0 -4
- data/lib/fontisan/svg/glyph_generator.rb +0 -2
- data/lib/fontisan/svg.rb +12 -0
- data/lib/fontisan/tables/cbdt.rb +0 -1
- data/lib/fontisan/tables/cblc.rb +0 -1
- data/lib/fontisan/tables/cff/charset.rb +0 -1
- data/lib/fontisan/tables/cff/charstring.rb +0 -1
- data/lib/fontisan/tables/cff/charstring_rebuilder.rb +0 -4
- data/lib/fontisan/tables/cff/charstrings_index.rb +0 -3
- data/lib/fontisan/tables/cff/dict.rb +0 -1
- data/lib/fontisan/tables/cff/encoding.rb +0 -1
- data/lib/fontisan/tables/cff/header.rb +0 -2
- data/lib/fontisan/tables/cff/hint_operation_injector.rb +0 -2
- data/lib/fontisan/tables/cff/index.rb +0 -1
- data/lib/fontisan/tables/cff/private_dict.rb +0 -2
- data/lib/fontisan/tables/cff/private_dict_writer.rb +0 -2
- data/lib/fontisan/tables/cff/table_builder.rb +0 -6
- data/lib/fontisan/tables/cff/top_dict.rb +0 -2
- data/lib/fontisan/tables/cff.rb +22 -15
- data/lib/fontisan/tables/cff2/charstring_parser.rb +0 -2
- data/lib/fontisan/tables/cff2/table_builder.rb +0 -11
- data/lib/fontisan/tables/cff2/table_reader.rb +0 -2
- data/lib/fontisan/tables/cff2.rb +13 -14
- data/lib/fontisan/tables/cmap.rb +24 -2
- data/lib/fontisan/tables/cmap_table.rb +0 -3
- data/lib/fontisan/tables/colr.rb +0 -1
- data/lib/fontisan/tables/cpal.rb +0 -1
- data/lib/fontisan/tables/cvar.rb +0 -2
- data/lib/fontisan/tables/fvar.rb +0 -1
- data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +0 -2
- data/lib/fontisan/tables/glyf/glyph_builder.rb +0 -3
- data/lib/fontisan/tables/glyf.rb +0 -6
- data/lib/fontisan/tables/glyf_table.rb +0 -3
- data/lib/fontisan/tables/gpos.rb +0 -2
- data/lib/fontisan/tables/gsub.rb +0 -2
- data/lib/fontisan/tables/gvar.rb +0 -2
- data/lib/fontisan/tables/head.rb +0 -2
- data/lib/fontisan/tables/head_table.rb +0 -3
- data/lib/fontisan/tables/hhea.rb +0 -2
- data/lib/fontisan/tables/hhea_table.rb +0 -3
- data/lib/fontisan/tables/hmtx.rb +0 -2
- data/lib/fontisan/tables/hmtx_table.rb +0 -3
- data/lib/fontisan/tables/hvar.rb +0 -3
- data/lib/fontisan/tables/loca.rb +0 -2
- data/lib/fontisan/tables/loca_table.rb +0 -3
- data/lib/fontisan/tables/maxp.rb +0 -2
- data/lib/fontisan/tables/maxp_table.rb +0 -3
- data/lib/fontisan/tables/mvar.rb +0 -3
- data/lib/fontisan/tables/name.rb +0 -2
- data/lib/fontisan/tables/name_table.rb +0 -3
- data/lib/fontisan/tables/os2_table.rb +0 -3
- data/lib/fontisan/tables/post_table.rb +0 -3
- data/lib/fontisan/tables/sbix.rb +0 -1
- data/lib/fontisan/tables/svg.rb +0 -1
- data/lib/fontisan/tables/variation_common.rb +0 -1
- data/lib/fontisan/tables/vvar.rb +0 -3
- data/lib/fontisan/tables.rb +54 -0
- data/lib/fontisan/true_type_collection.rb +6 -14
- data/lib/fontisan/true_type_font.rb +8 -2
- data/lib/fontisan/true_type_font_extensions.rb +9 -9
- data/lib/fontisan/type1/afm_generator.rb +0 -4
- data/lib/fontisan/type1/conversion_options.rb +0 -2
- data/lib/fontisan/type1/encodings.rb +0 -2
- data/lib/fontisan/type1/generator.rb +0 -8
- data/lib/fontisan/type1/pfa_generator.rb +0 -3
- data/lib/fontisan/type1/pfb_generator.rb +0 -5
- data/lib/fontisan/type1/pfm_generator.rb +0 -4
- data/lib/fontisan/type1.rb +42 -69
- data/lib/fontisan/type1_font.rb +40 -11
- data/lib/fontisan/ucd/aggregator.rb +73 -0
- data/lib/fontisan/ucd/cache_manager.rb +111 -0
- data/lib/fontisan/ucd/config.rb +59 -0
- data/lib/fontisan/ucd/download_error.rb +9 -0
- data/lib/fontisan/ucd/downloader.rb +88 -0
- data/lib/fontisan/ucd/error.rb +8 -0
- data/lib/fontisan/ucd/index.rb +103 -0
- data/lib/fontisan/ucd/index_builder.rb +107 -0
- data/lib/fontisan/ucd/range_entry.rb +56 -0
- data/lib/fontisan/ucd/unknown_version_error.rb +9 -0
- data/lib/fontisan/ucd/version_resolver.rb +79 -0
- data/lib/fontisan/ucd.rb +23 -0
- data/lib/fontisan/utilities/checksum_calculator.rb +0 -1
- data/lib/fontisan/utilities.rb +10 -0
- data/lib/fontisan/utils.rb +10 -0
- data/lib/fontisan/validation/collection_validator.rb +0 -2
- data/lib/fontisan/validation.rb +9 -0
- data/lib/fontisan/validators/basic_validator.rb +0 -2
- data/lib/fontisan/validators/font_book_validator.rb +0 -2
- data/lib/fontisan/validators/opentype_validator.rb +0 -2
- data/lib/fontisan/validators/profile_loader.rb +0 -5
- data/lib/fontisan/validators/validator.rb +0 -2
- data/lib/fontisan/validators/web_font_validator.rb +0 -2
- data/lib/fontisan/validators.rb +14 -0
- data/lib/fontisan/variable/delta_applicator.rb +0 -4
- data/lib/fontisan/variable/instancer.rb +0 -3
- data/lib/fontisan/variable/static_font_builder.rb +0 -3
- data/lib/fontisan/variable.rb +16 -0
- data/lib/fontisan/variation/blend_applier.rb +0 -2
- data/lib/fontisan/variation/cache.rb +0 -2
- data/lib/fontisan/variation/converter.rb +0 -3
- data/lib/fontisan/variation/data_extractor.rb +0 -2
- data/lib/fontisan/variation/delta_applier.rb +0 -5
- data/lib/fontisan/variation/inspector.rb +0 -1
- data/lib/fontisan/variation/instance_generator.rb +0 -6
- data/lib/fontisan/variation/instance_writer.rb +0 -5
- data/lib/fontisan/variation/metrics_adjuster.rb +0 -4
- data/lib/fontisan/variation/optimizer.rb +0 -3
- data/lib/fontisan/variation/parallel_generator.rb +0 -3
- data/lib/fontisan/variation/subsetter.rb +0 -4
- data/lib/fontisan/variation/tuple_variation_header.rb +0 -2
- data/lib/fontisan/variation/variable_svg_generator.rb +0 -3
- data/lib/fontisan/variation/variation_context.rb +0 -3
- data/lib/fontisan/variation/variation_preserver.rb +0 -3
- data/lib/fontisan/variation.rb +31 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2.rb +13 -0
- data/lib/fontisan/woff2_font.rb +31 -9
- data/lib/fontisan/woff_font.rb +31 -2
- data/lib/fontisan.rb +124 -196
- metadata +114 -7
- data/fontisan.gemspec +0 -48
|
@@ -1,41 +1,34 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "zlib"
|
|
4
|
-
require_relative "conversion_strategy"
|
|
5
|
-
require_relative "../utilities/checksum_calculator"
|
|
6
4
|
|
|
7
5
|
module Fontisan
|
|
8
6
|
module Converters
|
|
9
7
|
# WOFF font writer for creating WOFF files from TTF/OTF fonts
|
|
10
8
|
#
|
|
11
|
-
# [`WoffWriter`](lib/fontisan/converters/woff_writer.rb)
|
|
12
|
-
#
|
|
13
|
-
#
|
|
9
|
+
# [`WoffWriter`](lib/fontisan/converters/woff_writer.rb) converts
|
|
10
|
+
# TrueType/OpenType fonts to WOFF 1.0 format. The WOFF spec mandates zlib
|
|
11
|
+
# compression; this writer exposes the spec-legal knobs only:
|
|
14
12
|
#
|
|
15
|
-
#
|
|
16
|
-
# -
|
|
17
|
-
#
|
|
18
|
-
# -
|
|
19
|
-
#
|
|
20
|
-
# -
|
|
13
|
+
# - `zlib_level` (0–9) — zlib compression level
|
|
14
|
+
# - `uncompressed` (bool) — store tables uncompressed (legal per WOFF 1.0
|
|
15
|
+
# §5.1; `compLength == origLength`)
|
|
16
|
+
# - `compression_threshold` (bytes) — skip compression for tables smaller
|
|
17
|
+
# than N bytes (rarely needed; keeps tiny tables uncompressed)
|
|
18
|
+
# - `metadata_xml` (string) — optional metadata block
|
|
19
|
+
# - `private_data` (string) — optional private data block
|
|
21
20
|
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# - Tables smaller than compression threshold remain uncompressed
|
|
25
|
-
# - Metadata and private data are compressed when present
|
|
26
|
-
# - All data is properly aligned and padded
|
|
21
|
+
# Cross-format options (e.g., `brotli_quality`) are rejected by
|
|
22
|
+
# ConversionStrategy#validate_options! — see {ConversionStrategy}.
|
|
27
23
|
#
|
|
28
|
-
# @example
|
|
29
|
-
# writer =
|
|
30
|
-
#
|
|
31
|
-
# File.
|
|
24
|
+
# @example Convert TTF to WOFF with max zlib
|
|
25
|
+
# writer = WoffWriter.new
|
|
26
|
+
# woff = writer.convert(ttf_font, zlib_level: 9)
|
|
27
|
+
# File.binwrite("out.woff", woff)
|
|
32
28
|
#
|
|
33
|
-
# @example
|
|
34
|
-
# writer =
|
|
35
|
-
#
|
|
36
|
-
# compression_threshold: 100 # Bytes - tables smaller than this stay uncompressed
|
|
37
|
-
# )
|
|
38
|
-
# woff_data = writer.write_font(ttf_font)
|
|
29
|
+
# @example Uncompressed WOFF (legal per spec; useful for tooling pipelines)
|
|
30
|
+
# writer = WoffWriter.new
|
|
31
|
+
# woff = writer.convert(ttf_font, uncompressed: true)
|
|
39
32
|
class WoffWriter
|
|
40
33
|
include ConversionStrategy
|
|
41
34
|
|
|
@@ -46,57 +39,57 @@ module Fontisan
|
|
|
46
39
|
WOFF_VERSION_MAJOR = 1
|
|
47
40
|
WOFF_VERSION_MINOR = 0
|
|
48
41
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
#
|
|
72
|
-
def initialize(options = {})
|
|
73
|
-
@compression_level = options[:compression_level] || DEFAULT_COMPRESSION_LEVEL
|
|
74
|
-
@compression_threshold = options[:compression_threshold] || DEFAULT_COMPRESSION_THRESHOLD
|
|
75
|
-
@metadata_xml = options[:metadata_xml]
|
|
76
|
-
@private_data = options[:private_data]
|
|
77
|
-
|
|
78
|
-
validate_compression_level!
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Convert font to WOFF format
|
|
42
|
+
option :zlib_level, type: :integer, range: 0..9, default: 6,
|
|
43
|
+
cli: "--zlib-level=N",
|
|
44
|
+
desc: "zlib compression level (0=fastest, 9=smallest)"
|
|
45
|
+
option :uncompressed, type: :boolean, default: false,
|
|
46
|
+
cli: "--uncompressed",
|
|
47
|
+
desc: "store tables uncompressed (legal per WOFF 1.0 §5.1)"
|
|
48
|
+
option :compression_threshold, type: :integer,
|
|
49
|
+
range: 0..(2**31 - 1),
|
|
50
|
+
default: 100,
|
|
51
|
+
cli: "--compression-threshold=N",
|
|
52
|
+
desc: "skip compression for tables smaller than N bytes"
|
|
53
|
+
option :metadata_xml, type: :string, default: nil,
|
|
54
|
+
cli: "--metadata-xml=XML",
|
|
55
|
+
desc: "optional metadata XML block"
|
|
56
|
+
option :private_data, type: :string, default: nil,
|
|
57
|
+
cli: "--private-data=DATA",
|
|
58
|
+
desc: "optional private data block"
|
|
59
|
+
|
|
60
|
+
# Initialize writer. The writer is stateless per call; all knobs come
|
|
61
|
+
# through the per-convert options hash.
|
|
62
|
+
def initialize; end
|
|
63
|
+
|
|
64
|
+
# Convert font to WOFF format.
|
|
82
65
|
#
|
|
83
66
|
# @param font [TrueTypeFont, OpenTypeFont] Source font
|
|
84
|
-
# @param options [Hash]
|
|
67
|
+
# @param options [Hash{Symbol => Object}] Per-call options; see declared
|
|
68
|
+
# options above. Unknown keys (framework metadata like
|
|
69
|
+
# `target_format`) are tolerated silently. Cross-format misuse
|
|
70
|
+
# (`brotli_quality` on a WOFF target) is caught upstream by
|
|
71
|
+
# `FormatConverter.validate_options_for_target!`.
|
|
85
72
|
# @return [String] WOFF file data as binary string
|
|
86
|
-
# @raise [ArgumentError] if
|
|
73
|
+
# @raise [ArgumentError] if any declared option fails validation
|
|
74
|
+
# @raise [ArgumentError] if font does not respond to required methods
|
|
87
75
|
def convert(font, options = {})
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
76
|
+
self.class.validate_options!(strategy_options(options))
|
|
77
|
+
validate(font, :woff)
|
|
78
|
+
|
|
79
|
+
opts = self.class.default_options.merge(strategy_options(options))
|
|
80
|
+
write_font(
|
|
81
|
+
font,
|
|
82
|
+
zlib_level: opts[:zlib_level],
|
|
83
|
+
uncompressed: opts[:uncompressed],
|
|
84
|
+
compression_threshold: opts[:compression_threshold],
|
|
85
|
+
metadata: opts[:metadata_xml],
|
|
86
|
+
private_data: opts[:private_data],
|
|
87
|
+
)[:woff_binary]
|
|
95
88
|
end
|
|
96
89
|
|
|
97
|
-
# Get supported conversions
|
|
90
|
+
# Get supported conversions.
|
|
98
91
|
#
|
|
99
|
-
# @return [Array<Array<Symbol>>]
|
|
92
|
+
# @return [Array<Array<Symbol>>] Pairs this strategy handles
|
|
100
93
|
def supported_conversions
|
|
101
94
|
[
|
|
102
95
|
%i[ttf woff],
|
|
@@ -104,40 +97,19 @@ module Fontisan
|
|
|
104
97
|
]
|
|
105
98
|
end
|
|
106
99
|
|
|
107
|
-
#
|
|
100
|
+
# Validate that the given font can be converted to WOFF.
|
|
108
101
|
#
|
|
109
|
-
# @param font [
|
|
110
|
-
# @param
|
|
111
|
-
# @
|
|
112
|
-
# @
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
compressed_tables = compress_tables(tables_data)
|
|
119
|
-
|
|
120
|
-
# Build WOFF file
|
|
121
|
-
build_woff_file(compressed_tables, font, metadata, private_data)
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
private
|
|
125
|
-
|
|
126
|
-
# Validate compression level
|
|
127
|
-
#
|
|
128
|
-
# @raise [ArgumentError] if compression level is invalid
|
|
129
|
-
def validate_compression_level!
|
|
130
|
-
unless @compression_level.between?(0, 9)
|
|
131
|
-
raise ArgumentError,
|
|
132
|
-
"Compression level must be between 0 and 9, got #{@compression_level}"
|
|
102
|
+
# @param font [Object] Font to validate
|
|
103
|
+
# @param target_format [Symbol] Must be :woff
|
|
104
|
+
# @return [Boolean]
|
|
105
|
+
# @raise [ArgumentError] if font is nil or missing required methods
|
|
106
|
+
# @raise [Fontisan::Error] if target_format is not :woff
|
|
107
|
+
def validate(font, target_format)
|
|
108
|
+
unless target_format == :woff
|
|
109
|
+
raise Fontisan::Error,
|
|
110
|
+
"WoffWriter only supports conversion to woff, got: #{target_format}"
|
|
133
111
|
end
|
|
134
|
-
end
|
|
135
112
|
|
|
136
|
-
# Validate font for conversion
|
|
137
|
-
#
|
|
138
|
-
# @param font [TrueTypeFont, OpenTypeFont] Font to validate
|
|
139
|
-
# @raise [ArgumentError] if font is invalid
|
|
140
|
-
def validate_font(font)
|
|
141
113
|
raise ArgumentError, "Font cannot be nil" if font.nil?
|
|
142
114
|
|
|
143
115
|
unless font.respond_to?(:tables) && font.respond_to?(:table_data)
|
|
@@ -145,251 +117,208 @@ module Fontisan
|
|
|
145
117
|
end
|
|
146
118
|
end
|
|
147
119
|
|
|
148
|
-
#
|
|
120
|
+
# Write font to WOFF binary.
|
|
149
121
|
#
|
|
150
122
|
# @param font [TrueTypeFont, OpenTypeFont] Source font
|
|
151
|
-
# @
|
|
152
|
-
|
|
153
|
-
|
|
123
|
+
# @param zlib_level [Integer] 0–9
|
|
124
|
+
# @param uncompressed [Boolean] skip zlib; store as-is
|
|
125
|
+
# @param compression_threshold [Integer] skip compression below N bytes
|
|
126
|
+
# @param metadata [String, nil] optional metadata XML
|
|
127
|
+
# @param private_data [String, nil] optional private data
|
|
128
|
+
# @return [Hash{Symbol => String}] `{ woff_binary: <bytes> }`
|
|
129
|
+
def write_font(font, zlib_level:, uncompressed:, compression_threshold:,
|
|
130
|
+
metadata: nil, private_data: nil)
|
|
131
|
+
tables_data = collect_tables_data(font)
|
|
132
|
+
compressed_tables = compress_tables(
|
|
133
|
+
tables_data,
|
|
134
|
+
zlib_level: uncompressed ? 0 : zlib_level,
|
|
135
|
+
skip_compression: uncompressed,
|
|
136
|
+
compression_threshold: compression_threshold,
|
|
137
|
+
)
|
|
138
|
+
compressed_metadata = compress_metadata(metadata, zlib_level: zlib_level,
|
|
139
|
+
skip_compression: uncompressed)
|
|
140
|
+
binary = build_woff_file(compressed_tables, font, compressed_metadata,
|
|
141
|
+
private_data)
|
|
142
|
+
{ woff_binary: binary }
|
|
143
|
+
end
|
|
154
144
|
|
|
155
|
-
|
|
156
|
-
data = font.table_data[tag]
|
|
157
|
-
tables_data[tag] = data if data
|
|
158
|
-
end
|
|
145
|
+
private
|
|
159
146
|
|
|
160
|
-
|
|
147
|
+
# Slice options to those declared by this strategy. Tolerates extra
|
|
148
|
+
# keys (e.g., `target_format`) silently so FormatConverter can pass the
|
|
149
|
+
# full options hash through.
|
|
150
|
+
def strategy_options(options)
|
|
151
|
+
names = self.class.supported_options.to_set(&:name)
|
|
152
|
+
options.select { |k, _| names.include?(k.to_sym) }
|
|
161
153
|
end
|
|
162
154
|
|
|
163
|
-
#
|
|
155
|
+
# Collect all table data from font.
|
|
164
156
|
#
|
|
165
|
-
# @param
|
|
166
|
-
# @return [Hash<String,
|
|
167
|
-
def
|
|
168
|
-
|
|
157
|
+
# @param font [TrueTypeFont, OpenTypeFont]
|
|
158
|
+
# @return [Hash<String, String>]
|
|
159
|
+
def collect_tables_data(font)
|
|
160
|
+
font.table_names.to_h do |tag|
|
|
161
|
+
[tag, font.table_data[tag]]
|
|
162
|
+
end.compact
|
|
163
|
+
end
|
|
169
164
|
|
|
170
|
-
|
|
165
|
+
# Compress tables with zlib (or skip compression entirely).
|
|
166
|
+
#
|
|
167
|
+
# @param tables_data [Hash<String, String>]
|
|
168
|
+
# @param zlib_level [Integer] 0–9 (ignored if skip_compression)
|
|
169
|
+
# @param skip_compression [Boolean] store all tables uncompressed
|
|
170
|
+
# @param compression_threshold [Integer] tables below this size are kept
|
|
171
|
+
# uncompressed even when skip_compression is false
|
|
172
|
+
# @return [Hash<String, Hash>] per-table compressed info
|
|
173
|
+
def compress_tables(tables_data, zlib_level:, skip_compression:,
|
|
174
|
+
compression_threshold:)
|
|
175
|
+
tables_data.to_h do |tag, data|
|
|
171
176
|
original_size = data.bytesize
|
|
177
|
+
should_compress =
|
|
178
|
+
!skip_compression && original_size >= compression_threshold
|
|
172
179
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
compressed_size = compressed_data.bytesize
|
|
177
|
-
|
|
178
|
-
# Only use compression if it actually reduces size
|
|
179
|
-
compressed_tables[tag] = if compressed_size < original_size
|
|
180
|
-
{
|
|
181
|
-
original_data: data,
|
|
182
|
-
compressed_data: compressed_data,
|
|
183
|
-
original_length: original_size,
|
|
184
|
-
compressed_length: compressed_size,
|
|
185
|
-
is_compressed: true,
|
|
186
|
-
}
|
|
187
|
-
else
|
|
188
|
-
# Compression didn't help, store uncompressed
|
|
189
|
-
{
|
|
190
|
-
original_data: data,
|
|
191
|
-
compressed_data: data,
|
|
192
|
-
original_length: original_size,
|
|
193
|
-
compressed_length: original_size,
|
|
194
|
-
is_compressed: false,
|
|
195
|
-
}
|
|
196
|
-
end
|
|
180
|
+
if should_compress
|
|
181
|
+
compressed = Zlib::Deflate.deflate(data, zlib_level)
|
|
182
|
+
use_compressed = compressed.bytesize < original_size
|
|
197
183
|
else
|
|
198
|
-
|
|
199
|
-
|
|
184
|
+
use_compressed = false
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
[
|
|
188
|
+
tag,
|
|
189
|
+
{
|
|
200
190
|
original_data: data,
|
|
201
|
-
compressed_data: data,
|
|
191
|
+
compressed_data: use_compressed ? compressed : data,
|
|
202
192
|
original_length: original_size,
|
|
203
|
-
compressed_length: original_size,
|
|
204
|
-
is_compressed:
|
|
205
|
-
}
|
|
206
|
-
|
|
193
|
+
compressed_length: use_compressed ? compressed.bytesize : original_size,
|
|
194
|
+
is_compressed: use_compressed,
|
|
195
|
+
},
|
|
196
|
+
]
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Compress metadata with zlib.
|
|
201
|
+
#
|
|
202
|
+
# @param metadata [String, nil]
|
|
203
|
+
# @param zlib_level [Integer]
|
|
204
|
+
# @param skip_compression [Boolean]
|
|
205
|
+
# @return [Hash, nil]
|
|
206
|
+
def compress_metadata(metadata, zlib_level:, skip_compression:)
|
|
207
|
+
return nil unless metadata
|
|
208
|
+
|
|
209
|
+
original_length = metadata.bytesize
|
|
210
|
+
if skip_compression
|
|
211
|
+
return {
|
|
212
|
+
original_data: metadata,
|
|
213
|
+
compressed_data: metadata,
|
|
214
|
+
original_length: original_length,
|
|
215
|
+
compressed_length: original_length,
|
|
216
|
+
}
|
|
207
217
|
end
|
|
208
218
|
|
|
209
|
-
|
|
219
|
+
compressed = Zlib::Deflate.deflate(metadata, zlib_level)
|
|
220
|
+
use_compressed = compressed.bytesize < original_length
|
|
221
|
+
{
|
|
222
|
+
original_data: metadata,
|
|
223
|
+
compressed_data: use_compressed ? compressed : metadata,
|
|
224
|
+
original_length: original_length,
|
|
225
|
+
compressed_length: use_compressed ? compressed.bytesize : original_length,
|
|
226
|
+
}
|
|
210
227
|
end
|
|
211
228
|
|
|
212
|
-
#
|
|
229
|
+
# Assemble complete WOFF binary.
|
|
213
230
|
#
|
|
214
|
-
# @param compressed_tables [Hash]
|
|
215
|
-
# @param font [TrueTypeFont, OpenTypeFont]
|
|
216
|
-
# @param
|
|
217
|
-
# @param private_data [String, nil]
|
|
218
|
-
# @return [String]
|
|
219
|
-
def build_woff_file(compressed_tables, font,
|
|
231
|
+
# @param compressed_tables [Hash]
|
|
232
|
+
# @param font [TrueTypeFont, OpenTypeFont]
|
|
233
|
+
# @param compressed_metadata [Hash, nil]
|
|
234
|
+
# @param private_data [String, nil]
|
|
235
|
+
# @return [String]
|
|
236
|
+
def build_woff_file(compressed_tables, font, compressed_metadata,
|
|
237
|
+
private_data)
|
|
220
238
|
io = StringIO.new
|
|
221
239
|
io.set_encoding(Encoding::BINARY)
|
|
222
240
|
|
|
223
|
-
|
|
224
|
-
compressed_metadata = compress_metadata(metadata)
|
|
225
|
-
|
|
226
|
-
# Calculate offsets and sizes
|
|
227
|
-
header_size = 44 # WOFF header size
|
|
241
|
+
header_size = 44
|
|
228
242
|
num_tables = compressed_tables.length
|
|
229
|
-
table_dir_size = num_tables * 20
|
|
230
|
-
|
|
231
|
-
# Calculate data offset (after header + table directory)
|
|
243
|
+
table_dir_size = num_tables * 20
|
|
232
244
|
data_offset = header_size + table_dir_size
|
|
233
|
-
|
|
234
|
-
# Calculate metadata and private data offsets
|
|
235
245
|
metadata_offset = data_offset
|
|
236
246
|
metadata_size = compressed_metadata ? compressed_metadata[:compressed_length] : 0
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
total_compressed_size = compressed_tables.values.sum do |table|
|
|
240
|
-
table[:compressed_length]
|
|
247
|
+
total_compressed_size = compressed_tables.values.sum do |t|
|
|
248
|
+
t[:compressed_length]
|
|
241
249
|
end
|
|
242
|
-
|
|
243
|
-
# Calculate private data offset (after table data + metadata)
|
|
244
250
|
private_offset = data_offset + total_compressed_size + metadata_size
|
|
245
251
|
private_size = private_data ? private_data.bytesize : 0
|
|
246
|
-
|
|
247
|
-
# Calculate total WOFF file size
|
|
248
252
|
total_size = private_offset + private_size
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
total_sfnt_size = compressed_tables.values.sum do |table|
|
|
252
|
-
table[:original_length]
|
|
253
|
+
total_sfnt_size = compressed_tables.values.sum do |t|
|
|
254
|
+
t[:original_length]
|
|
253
255
|
end +
|
|
254
256
|
header_size + table_dir_size
|
|
255
257
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
# Write table directory
|
|
258
|
+
write_woff_header(
|
|
259
|
+
io, font, total_size, total_sfnt_size, num_tables,
|
|
260
|
+
compressed_metadata, metadata_offset, metadata_size,
|
|
261
|
+
private_offset, private_size
|
|
262
|
+
)
|
|
262
263
|
write_table_directory(io, compressed_tables, data_offset)
|
|
263
|
-
|
|
264
|
-
# Write compressed table data
|
|
265
264
|
write_compressed_table_data(io, compressed_tables)
|
|
266
|
-
|
|
267
|
-
# Write compressed metadata if present
|
|
268
265
|
write_metadata(io, compressed_metadata) if compressed_metadata
|
|
269
|
-
|
|
270
|
-
# Write private data if present
|
|
271
266
|
write_private_data(io, private_data) if private_data
|
|
272
267
|
|
|
273
268
|
io.string
|
|
274
269
|
end
|
|
275
270
|
|
|
276
|
-
#
|
|
277
|
-
#
|
|
278
|
-
# @param metadata [String, nil] Metadata XML
|
|
279
|
-
# @return [Hash, nil] Compressed metadata info or nil
|
|
280
|
-
def compress_metadata(metadata)
|
|
281
|
-
return nil unless metadata
|
|
282
|
-
|
|
283
|
-
original_length = metadata.bytesize
|
|
284
|
-
compressed_data = Zlib::Deflate.deflate(metadata, @compression_level)
|
|
285
|
-
compressed_length = compressed_data.bytesize
|
|
286
|
-
|
|
287
|
-
{
|
|
288
|
-
original_data: metadata,
|
|
289
|
-
compressed_data: compressed_data,
|
|
290
|
-
original_length: original_length,
|
|
291
|
-
compressed_length: compressed_length,
|
|
292
|
-
}
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
# Write WOFF header
|
|
296
|
-
#
|
|
297
|
-
# @param io [StringIO] Output stream
|
|
298
|
-
# @param font [TrueTypeFont, OpenTypeFont] Source font
|
|
299
|
-
# @param total_size [Integer] Total WOFF file size
|
|
300
|
-
# @param total_sfnt_size [Integer] Uncompressed SFNT size
|
|
301
|
-
# @param num_tables [Integer] Number of tables
|
|
302
|
-
# @param compressed_metadata [Hash, nil] Compressed metadata info
|
|
303
|
-
# @param metadata_offset [Integer] Metadata offset
|
|
304
|
-
# @param metadata_size [Integer] Compressed metadata size
|
|
305
|
-
# @param private_offset [Integer] Private data offset
|
|
306
|
-
# @param private_size [Integer] Private data size
|
|
307
|
-
# @return [void]
|
|
271
|
+
# Write WOFF header (44 bytes).
|
|
308
272
|
def write_woff_header(io, font, total_size, total_sfnt_size, num_tables,
|
|
309
273
|
compressed_metadata, metadata_offset, metadata_size,
|
|
310
274
|
private_offset, private_size)
|
|
311
|
-
# Determine flavor from font
|
|
312
275
|
flavor = if font.respond_to?(:cff?) && font.cff?
|
|
313
276
|
Constants::SFNT_VERSION_OTTO
|
|
314
277
|
else
|
|
315
|
-
# Default to TrueType for TrueType fonts and unknown types
|
|
316
278
|
Constants::SFNT_VERSION_TRUETYPE
|
|
317
279
|
end
|
|
318
280
|
|
|
319
|
-
#
|
|
320
|
-
io.write([
|
|
321
|
-
io.write([
|
|
322
|
-
io.write([
|
|
323
|
-
io.write([
|
|
324
|
-
io.write([
|
|
325
|
-
io.write([
|
|
326
|
-
io.write([
|
|
327
|
-
io.write([
|
|
328
|
-
io.write([
|
|
329
|
-
io.write([
|
|
330
|
-
io.write([
|
|
331
|
-
io.write([
|
|
332
|
-
io.write([private_size].pack("N")) # privLength (4 bytes)
|
|
281
|
+
io.write([WOFF_SIGNATURE].pack("N")) # signature
|
|
282
|
+
io.write([flavor].pack("N")) # flavor
|
|
283
|
+
io.write([total_size].pack("N")) # length
|
|
284
|
+
io.write([num_tables].pack("n")) # numTables
|
|
285
|
+
io.write([0].pack("n")) # reserved
|
|
286
|
+
io.write([total_sfnt_size].pack("N")) # totalSfntSize
|
|
287
|
+
io.write([WOFF_VERSION_MAJOR].pack("n")) # majorVersion
|
|
288
|
+
io.write([WOFF_VERSION_MINOR].pack("n")) # minorVersion
|
|
289
|
+
io.write([metadata_offset].pack("N")) # metaOffset
|
|
290
|
+
io.write([metadata_size].pack("N")) # metaLength
|
|
291
|
+
io.write([compressed_metadata ? compressed_metadata[:original_length] : 0].pack("N")) # metaOrigLength
|
|
292
|
+
io.write([private_offset].pack("N")) # privOffset
|
|
293
|
+
io.write([private_size].pack("N")) # privLength
|
|
333
294
|
end
|
|
334
295
|
|
|
335
|
-
# Write table directory
|
|
336
|
-
#
|
|
337
|
-
# @param io [StringIO] Output stream
|
|
338
|
-
# @param compressed_tables [Hash] Compressed table information
|
|
339
|
-
# @param data_offset [Integer] Starting offset for table data
|
|
340
|
-
# @return [void]
|
|
296
|
+
# Write table directory entries (20 bytes each).
|
|
341
297
|
def write_table_directory(io, compressed_tables, data_offset)
|
|
342
298
|
current_offset = data_offset
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
#
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
io.write(tag) # tag (4 bytes)
|
|
353
|
-
io.write([current_offset].pack("N")) # offset (4 bytes)
|
|
354
|
-
io.write([table_info[:compressed_length]].pack("N")) # compLength (4 bytes)
|
|
355
|
-
io.write([table_info[:original_length]].pack("N")) # origLength (4 bytes)
|
|
356
|
-
io.write([checksum].pack("N")) # origChecksum (4 bytes)
|
|
357
|
-
|
|
358
|
-
# Update offset for next table
|
|
359
|
-
current_offset += table_info[:compressed_length]
|
|
299
|
+
compressed_tables.sort_by { |tag, _| tag }.each do |tag, info|
|
|
300
|
+
checksum = Utilities::ChecksumCalculator
|
|
301
|
+
.calculate_table_checksum(info[:original_data])
|
|
302
|
+
io.write(tag) # tag
|
|
303
|
+
io.write([current_offset].pack("N")) # offset
|
|
304
|
+
io.write([info[:compressed_length]].pack("N")) # compLength
|
|
305
|
+
io.write([info[:original_length]].pack("N")) # origLength
|
|
306
|
+
io.write([checksum].pack("N")) # origChecksum
|
|
307
|
+
current_offset += info[:compressed_length]
|
|
360
308
|
end
|
|
361
309
|
end
|
|
362
310
|
|
|
363
|
-
# Write compressed table data
|
|
364
|
-
#
|
|
365
|
-
# @param io [StringIO] Output stream
|
|
366
|
-
# @param compressed_tables [Hash] Compressed table information
|
|
367
|
-
# @return [void]
|
|
311
|
+
# Write compressed table data, sorted by tag (matches directory order).
|
|
368
312
|
def write_compressed_table_data(io, compressed_tables)
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
# rubocop:disable Style/HashEachMethods - sorted_tables is an Array, not a Hash
|
|
373
|
-
sorted_tables.each do |_, table_info|
|
|
374
|
-
io.write(table_info[:compressed_data])
|
|
313
|
+
compressed_tables.sort_by { |tag, _| tag }.each do |_, info|
|
|
314
|
+
io.write(info[:compressed_data])
|
|
375
315
|
end
|
|
376
|
-
# rubocop:enable Style/HashEachMethods
|
|
377
316
|
end
|
|
378
317
|
|
|
379
|
-
# Write metadata to output
|
|
380
|
-
#
|
|
381
|
-
# @param io [StringIO] Output stream
|
|
382
|
-
# @param compressed_metadata [Hash] Compressed metadata info
|
|
383
|
-
# @return [void]
|
|
384
318
|
def write_metadata(io, compressed_metadata)
|
|
385
319
|
io.write(compressed_metadata[:compressed_data])
|
|
386
320
|
end
|
|
387
321
|
|
|
388
|
-
# Write private data to output
|
|
389
|
-
#
|
|
390
|
-
# @param io [StringIO] Output stream
|
|
391
|
-
# @param private_data [String] Private data
|
|
392
|
-
# @return [void]
|
|
393
322
|
def write_private_data(io, private_data)
|
|
394
323
|
io.write(private_data)
|
|
395
324
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Autoload hub for the Fontisan::Converters namespace.
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Converters
|
|
7
|
+
autoload :CffTableBuilder, "fontisan/converters/cff_table_builder"
|
|
8
|
+
autoload :CollectionConverter, "fontisan/converters/collection_converter"
|
|
9
|
+
autoload :ConversionStrategy, "fontisan/converters/conversion_strategy"
|
|
10
|
+
autoload :FormatConverter, "fontisan/converters/format_converter"
|
|
11
|
+
autoload :GlyfTableBuilder, "fontisan/converters/glyf_table_builder"
|
|
12
|
+
autoload :OutlineConverter, "fontisan/converters/outline_converter"
|
|
13
|
+
autoload :OutlineExtraction, "fontisan/converters/outline_extraction"
|
|
14
|
+
autoload :OutlineOptimizer, "fontisan/converters/outline_optimizer"
|
|
15
|
+
autoload :SvgGenerator, "fontisan/converters/svg_generator"
|
|
16
|
+
autoload :TableCopier, "fontisan/converters/table_copier"
|
|
17
|
+
autoload :Type1Converter, "fontisan/converters/type1_converter"
|
|
18
|
+
autoload :Woff2Encoder, "fontisan/converters/woff2_encoder"
|
|
19
|
+
autoload :WoffWriter, "fontisan/converters/woff_writer"
|
|
20
|
+
end
|
|
21
|
+
end
|