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,14 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
|
-
require_relative "upm_scaler"
|
|
5
|
-
require_relative "encodings"
|
|
6
|
-
require_relative "conversion_options"
|
|
7
|
-
require_relative "afm_generator"
|
|
8
|
-
require_relative "pfm_generator"
|
|
9
|
-
require_relative "pfa_generator"
|
|
10
|
-
require_relative "pfb_generator"
|
|
11
|
-
require_relative "inf_generator"
|
|
12
4
|
|
|
13
5
|
module Fontisan
|
|
14
6
|
module Type1
|
data/lib/fontisan/type1.rb
CHANGED
|
@@ -1,75 +1,48 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Autoload hub for the Fontisan::Type1 namespace.
|
|
4
|
+
#
|
|
5
|
+
# Adobe Type 1 Font support — PFB/PFA parsing, eexec decryption,
|
|
6
|
+
# CharString decryption, font dictionary parsing, conversion from
|
|
7
|
+
# TTF/OTF, UPM scaling, and multiple encoding support.
|
|
8
|
+
#
|
|
9
|
+
# @example Generate Type 1 formats from TTF
|
|
10
|
+
# font = Fontisan::FontLoader.load("font.ttf")
|
|
11
|
+
# result = Fontisan::Type1::Generator.generate(font)
|
|
12
|
+
# result[:afm] # => AFM file content
|
|
13
|
+
# result[:pfm] # => PFM file content
|
|
14
|
+
# result[:pfb] # => PFB file content
|
|
15
|
+
# result[:inf] # => INF file content
|
|
16
|
+
#
|
|
17
|
+
# @example Generate with specific options
|
|
18
|
+
# options = Fontisan::Type1::ConversionOptions.windows_type1
|
|
19
|
+
# result = Fontisan::Type1::Generator.generate(font, options)
|
|
20
|
+
#
|
|
21
|
+
# @see https://www.adobe.com/devnet/font/pdfs/Type1.pdf
|
|
22
|
+
|
|
3
23
|
module Fontisan
|
|
4
|
-
# Adobe Type 1 Font support
|
|
5
|
-
#
|
|
6
|
-
# [`Type1`](lib/fontisan/type1.rb) provides parsing and conversion
|
|
7
|
-
# capabilities for Adobe Type 1 fonts in PFB (Printer Font Binary)
|
|
8
|
-
# and PFA (Printer Font ASCII) formats.
|
|
9
|
-
#
|
|
10
|
-
# Type 1 fonts were the standard for digital typography in the 1980s-1990s
|
|
11
|
-
# and are still encountered in legacy systems and design workflows.
|
|
12
|
-
#
|
|
13
|
-
# Key features:
|
|
14
|
-
# - PFB and PFA format parsing
|
|
15
|
-
# - eexec decryption for encrypted font portions
|
|
16
|
-
# - CharString decryption with lenIV handling
|
|
17
|
-
# - Font dictionary parsing (FontInfo, Private dict)
|
|
18
|
-
# - Conversion from TTF/OTF to Type 1 formats
|
|
19
|
-
# - UPM scaling for Type 1 compatibility (1000 UPM)
|
|
20
|
-
# - Multiple encoding support (AdobeStandard, ISOLatin1, Unicode)
|
|
21
|
-
#
|
|
22
|
-
# @example Generate Type 1 formats from TTF
|
|
23
|
-
# font = Fontisan::FontLoader.load("font.ttf")
|
|
24
|
-
# result = Fontisan::Type1::Generator.generate(font)
|
|
25
|
-
# result[:afm] # => AFM file content
|
|
26
|
-
# result[:pfm] # => PFM file content
|
|
27
|
-
# result[:pfb] # => PFB file content
|
|
28
|
-
# result[:inf] # => INF file content
|
|
29
|
-
#
|
|
30
|
-
# @example Generate with specific options
|
|
31
|
-
# options = Fontisan::Type1::ConversionOptions.windows_type1
|
|
32
|
-
# result = Fontisan::Type1::Generator.generate(font, options)
|
|
33
|
-
#
|
|
34
|
-
# @see https://www.adobe.com/devnet/font/pdfs/Type1.pdf
|
|
35
24
|
module Type1
|
|
25
|
+
autoload :AGL, "fontisan/type1/agl"
|
|
26
|
+
autoload :AFMGenerator, "fontisan/type1/afm_generator"
|
|
27
|
+
autoload :AFMParser, "fontisan/type1/afm_parser"
|
|
28
|
+
autoload :CffToType1Converter, "fontisan/type1/cff_to_type1_converter"
|
|
29
|
+
autoload :CharStringConverter, "fontisan/type1/charstring_converter"
|
|
30
|
+
autoload :CharStrings, "fontisan/type1/charstrings"
|
|
31
|
+
autoload :ConversionOptions, "fontisan/type1/conversion_options"
|
|
32
|
+
autoload :Decryptor, "fontisan/type1/decryptor"
|
|
33
|
+
autoload :Encodings, "fontisan/type1/encodings"
|
|
34
|
+
autoload :FontDictionary, "fontisan/type1/font_dictionary"
|
|
35
|
+
autoload :Generator, "fontisan/type1/generator"
|
|
36
|
+
autoload :INFGenerator, "fontisan/type1/inf_generator"
|
|
37
|
+
autoload :PFAGenerator, "fontisan/type1/pfa_generator"
|
|
38
|
+
autoload :PFAParser, "fontisan/type1/pfa_parser"
|
|
39
|
+
autoload :PFBGenerator, "fontisan/type1/pfb_generator"
|
|
40
|
+
autoload :PFBParser, "fontisan/type1/pfb_parser"
|
|
41
|
+
autoload :PFMGenerator, "fontisan/type1/pfm_generator"
|
|
42
|
+
autoload :PFMParser, "fontisan/type1/pfm_parser"
|
|
43
|
+
autoload :PrivateDict, "fontisan/type1/private_dict"
|
|
44
|
+
autoload :SeacExpander, "fontisan/type1/seac_expander"
|
|
45
|
+
autoload :TTFToType1Converter, "fontisan/type1/ttf_to_type1_converter"
|
|
46
|
+
autoload :UPMScaler, "fontisan/type1/upm_scaler"
|
|
36
47
|
end
|
|
37
48
|
end
|
|
38
|
-
|
|
39
|
-
# Parsers
|
|
40
|
-
require_relative "type1/pfb_parser"
|
|
41
|
-
require_relative "type1/pfa_parser"
|
|
42
|
-
|
|
43
|
-
# Core components
|
|
44
|
-
require_relative "type1/decryptor"
|
|
45
|
-
require_relative "type1/font_dictionary"
|
|
46
|
-
require_relative "type1/private_dict"
|
|
47
|
-
require_relative "type1/charstrings"
|
|
48
|
-
require_relative "type1/charstring_converter"
|
|
49
|
-
require_relative "type1/cff_to_type1_converter"
|
|
50
|
-
require_relative "type1/seac_expander"
|
|
51
|
-
|
|
52
|
-
# Infrastructure
|
|
53
|
-
require_relative "type1/upm_scaler"
|
|
54
|
-
require_relative "type1/agl"
|
|
55
|
-
require_relative "type1/encodings"
|
|
56
|
-
require_relative "type1/conversion_options"
|
|
57
|
-
|
|
58
|
-
# TTF to Type 1 conversion
|
|
59
|
-
require_relative "type1/ttf_to_type1_converter"
|
|
60
|
-
|
|
61
|
-
# Metrics parsers
|
|
62
|
-
require_relative "type1/afm_parser"
|
|
63
|
-
require_relative "type1/pfm_parser"
|
|
64
|
-
|
|
65
|
-
# Metrics generators
|
|
66
|
-
require_relative "type1/afm_generator"
|
|
67
|
-
require_relative "type1/pfm_generator"
|
|
68
|
-
|
|
69
|
-
# Type 1 font generators
|
|
70
|
-
require_relative "type1/pfa_generator"
|
|
71
|
-
require_relative "type1/pfb_generator"
|
|
72
|
-
require_relative "type1/inf_generator"
|
|
73
|
-
|
|
74
|
-
# Unified generator interface
|
|
75
|
-
require_relative "type1/generator"
|
data/lib/fontisan/type1_font.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "type1"
|
|
4
|
-
|
|
5
3
|
module Fontisan
|
|
6
4
|
# Adobe Type 1 Font handler
|
|
7
5
|
#
|
|
@@ -34,8 +32,38 @@ module Fontisan
|
|
|
34
32
|
# @return [String, nil] File path if loaded from file
|
|
35
33
|
attr_reader :file_path
|
|
36
34
|
|
|
37
|
-
# @return [Symbol]
|
|
38
|
-
|
|
35
|
+
# @return [Symbol] Container variant (:pfb or :pfa) — the on-disk
|
|
36
|
+
# encoding of a Type 1 font (binary vs ASCII). Distinct from
|
|
37
|
+
# {#format}, which is the high-level pipeline format.
|
|
38
|
+
attr_reader :container_format
|
|
39
|
+
|
|
40
|
+
# High-level pipeline format identifier. Owned by the font class so
|
|
41
|
+
# the conversion pipeline can dispatch without case statements (OCP).
|
|
42
|
+
#
|
|
43
|
+
# @return [Symbol] :type1
|
|
44
|
+
def format = :type1
|
|
45
|
+
|
|
46
|
+
# Whether this object represents a font collection rather than a single
|
|
47
|
+
# font. Each font class is the authority on this question.
|
|
48
|
+
#
|
|
49
|
+
# @return [Boolean]
|
|
50
|
+
def collection? = false
|
|
51
|
+
|
|
52
|
+
# Type 1 fonts are not OpenType variable fonts.
|
|
53
|
+
#
|
|
54
|
+
# @return [Symbol] :static
|
|
55
|
+
def variation_type = :static
|
|
56
|
+
|
|
57
|
+
# Outline representation. Type 1 fonts use cubic PostScript CharStrings.
|
|
58
|
+
#
|
|
59
|
+
# @return [Symbol] :postscript
|
|
60
|
+
def outline_type = :postscript
|
|
61
|
+
|
|
62
|
+
# Type 1 fonts have no SFNT table directory; the table-oriented pipeline
|
|
63
|
+
# introspection does not apply.
|
|
64
|
+
#
|
|
65
|
+
# @return [Array<String>] empty
|
|
66
|
+
def table_names = []
|
|
39
67
|
|
|
40
68
|
# @return [Symbol] Loading mode (:metadata or :full)
|
|
41
69
|
attr_reader :loading_mode
|
|
@@ -64,12 +92,13 @@ module Fontisan
|
|
|
64
92
|
# Initialize a new Type1Font instance
|
|
65
93
|
#
|
|
66
94
|
# @param data [String] Font file data (binary or text)
|
|
67
|
-
# @param
|
|
95
|
+
# @param container_format [Symbol] Container variant (:pfb or :pfa,
|
|
96
|
+
# auto-detected if nil)
|
|
68
97
|
# @param file_path [String, nil] Optional file path for reference
|
|
69
98
|
# @param mode [Symbol] Loading mode (:metadata or :full, default: :full)
|
|
70
|
-
def initialize(data,
|
|
99
|
+
def initialize(data, container_format: nil, file_path: nil, mode: :full)
|
|
71
100
|
@file_path = file_path
|
|
72
|
-
@
|
|
101
|
+
@container_format = container_format || detect_format(data)
|
|
73
102
|
@data = data
|
|
74
103
|
@loading_mode = mode
|
|
75
104
|
|
|
@@ -139,7 +168,7 @@ module Fontisan
|
|
|
139
168
|
if @encrypted_portion.nil? || @encrypted_portion.empty?
|
|
140
169
|
@decrypted_data = @clear_text
|
|
141
170
|
else
|
|
142
|
-
encrypted_binary = if @
|
|
171
|
+
encrypted_binary = if @container_format == :pfa
|
|
143
172
|
# Convert hex string to binary
|
|
144
173
|
[@encrypted_portion.gsub(/\s/, "")].pack("H*")
|
|
145
174
|
else
|
|
@@ -226,15 +255,15 @@ module Fontisan
|
|
|
226
255
|
|
|
227
256
|
private
|
|
228
257
|
|
|
229
|
-
# Parse font data based on format
|
|
258
|
+
# Parse font data based on container format
|
|
230
259
|
def parse_font_data
|
|
231
|
-
case @
|
|
260
|
+
case @container_format
|
|
232
261
|
when :pfb
|
|
233
262
|
parse_pfb
|
|
234
263
|
when :pfa
|
|
235
264
|
parse_pfa
|
|
236
265
|
else
|
|
237
|
-
raise Fontisan::Error, "Unknown format: #{@
|
|
266
|
+
raise Fontisan::Error, "Unknown container format: #{@container_format}"
|
|
238
267
|
end
|
|
239
268
|
end
|
|
240
269
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Ucd
|
|
5
|
+
# Produces audit-ready aggregations from a codepoint list + UCD indices.
|
|
6
|
+
#
|
|
7
|
+
# Pure: no I/O, no side effects. Caller passes the codepoints and the
|
|
8
|
+
# blocks/scripts indices; Aggregator returns the aggregated summaries.
|
|
9
|
+
module Aggregator
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# Aggregate codepoints per Unicode block.
|
|
13
|
+
#
|
|
14
|
+
# Returns one hash per overlapping block, sorted by first_cp:
|
|
15
|
+
#
|
|
16
|
+
# { name:, first_cp:, last_cp:, total:, covered:, fill_ratio:, complete: }
|
|
17
|
+
#
|
|
18
|
+
# @param codepoints [Array<Integer>] sorted not required
|
|
19
|
+
# @param blocks_index [Index]
|
|
20
|
+
# @return [Array<Hash>]
|
|
21
|
+
def aggregate_blocks(codepoints, blocks_index)
|
|
22
|
+
sorted = codepoints.sort
|
|
23
|
+
return [] if sorted.empty?
|
|
24
|
+
|
|
25
|
+
coverage = Hash.new { |h, k| h[k] = 0 }
|
|
26
|
+
coverage.compare_by_identity
|
|
27
|
+
first_cp = sorted.first
|
|
28
|
+
last_cp = sorted.last
|
|
29
|
+
|
|
30
|
+
overlapping = blocks_index.each_overlapping(first_cp, last_cp).to_a
|
|
31
|
+
overlapping.each do |entry|
|
|
32
|
+
coverage[entry] = count_in_range(sorted, [entry.first_cp, entry.last_cp])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
overlapping.map do |entry|
|
|
36
|
+
covered = coverage[entry]
|
|
37
|
+
total = entry.size
|
|
38
|
+
{
|
|
39
|
+
name: entry.name,
|
|
40
|
+
first_cp: entry.first_cp,
|
|
41
|
+
last_cp: entry.last_cp,
|
|
42
|
+
total: total,
|
|
43
|
+
covered: covered,
|
|
44
|
+
fill_ratio: covered.fdiv(total).round(4),
|
|
45
|
+
complete: covered == total,
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Aggregate unique script names from codepoints.
|
|
51
|
+
#
|
|
52
|
+
# @param codepoints [Array<Integer>]
|
|
53
|
+
# @param scripts_index [Index]
|
|
54
|
+
# @return [Array<String>] sorted unique script names
|
|
55
|
+
def aggregate_scripts(codepoints, scripts_index)
|
|
56
|
+
scripts = codepoints.filter_map { |cp| scripts_index.lookup(cp) }
|
|
57
|
+
scripts.uniq.sort
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Count codepoints in `sorted` that fall within [first, last].
|
|
61
|
+
# `sorted` must be sorted ascending.
|
|
62
|
+
def count_in_range(sorted, range)
|
|
63
|
+
first, last = range
|
|
64
|
+
left = sorted.bsearch_index { |cp| cp >= first } || sorted.size
|
|
65
|
+
return 0 if left == sorted.size
|
|
66
|
+
|
|
67
|
+
right = sorted.bsearch_index { |cp| cp > last } || sorted.size
|
|
68
|
+
right - left
|
|
69
|
+
end
|
|
70
|
+
private_class_method :count_in_range
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Ucd
|
|
7
|
+
# Manages the on-disk UCD cache layout.
|
|
8
|
+
#
|
|
9
|
+
# Cache root resolution honors `XDG_CONFIG_HOME` per the XDG Base
|
|
10
|
+
# Directory Specification. Falls back to `~/.config` on Unix and
|
|
11
|
+
# `~/.config` (literal) elsewhere — consistent with other Fontisan
|
|
12
|
+
# config paths.
|
|
13
|
+
#
|
|
14
|
+
# Layout:
|
|
15
|
+
#
|
|
16
|
+
# <root>/
|
|
17
|
+
# <version>/
|
|
18
|
+
# ucdxml/
|
|
19
|
+
# ucd.all.flat.xml
|
|
20
|
+
# index/
|
|
21
|
+
# blocks.yml
|
|
22
|
+
# scripts.yml
|
|
23
|
+
#
|
|
24
|
+
# No network access — all methods are pure filesystem operations.
|
|
25
|
+
module CacheManager
|
|
26
|
+
UCDXML_FILENAME = "ucd.all.flat.xml"
|
|
27
|
+
private_constant :UCDXML_FILENAME
|
|
28
|
+
|
|
29
|
+
BLOCKS_INDEX_FILENAME = "blocks.yml"
|
|
30
|
+
SCRIPTS_INDEX_FILENAME = "scripts.yml"
|
|
31
|
+
private_constant :BLOCKS_INDEX_FILENAME, :SCRIPTS_INDEX_FILENAME
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
# Root path of the UCD cache.
|
|
35
|
+
# @return [Pathname]
|
|
36
|
+
def root
|
|
37
|
+
base = xdg_config_home || File.join(Dir.home, ".config")
|
|
38
|
+
Pathname.new(base).join("fontisan", "unicode")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Per-version directory.
|
|
42
|
+
# @param version [String] e.g. "17.0.0"
|
|
43
|
+
# @return [Pathname]
|
|
44
|
+
def version_dir(version)
|
|
45
|
+
root.join(version)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Path to the unpacked UCDXML flat file for a version.
|
|
49
|
+
# @param version [String]
|
|
50
|
+
# @return [Pathname]
|
|
51
|
+
def ucdxml_path(version)
|
|
52
|
+
version_dir(version).join("ucdxml", UCDXML_FILENAME)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Directory holding the derived RLE indices for a version.
|
|
56
|
+
# @param version [String]
|
|
57
|
+
# @return [Pathname]
|
|
58
|
+
def index_dir(version)
|
|
59
|
+
version_dir(version).join("index")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def blocks_index_path(version)
|
|
63
|
+
index_dir(version).join(BLOCKS_INDEX_FILENAME)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def scripts_index_path(version)
|
|
67
|
+
index_dir(version).join(SCRIPTS_INDEX_FILENAME)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# True if the UCDXML file is present for this version.
|
|
71
|
+
# @param version [String]
|
|
72
|
+
# @return [Boolean]
|
|
73
|
+
def cached?(version)
|
|
74
|
+
ucdxml_path(version).exist?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# All versions currently in the cache (sorted ascending).
|
|
78
|
+
# @return [Array<String>]
|
|
79
|
+
def cached_versions
|
|
80
|
+
return [] unless root.exist?
|
|
81
|
+
|
|
82
|
+
root.children.select(&:directory?).map { |p| p.basename.to_s }.sort
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Create the version directory and ucdxml/index subdirs.
|
|
86
|
+
# Idempotent.
|
|
87
|
+
# @param version [String]
|
|
88
|
+
def ensure_version_dir!(version)
|
|
89
|
+
ucdxml_path(version).dirname.mkpath
|
|
90
|
+
index_dir(version).mkpath
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Remove a version from the cache. No-op if absent.
|
|
94
|
+
# @param version [String]
|
|
95
|
+
def remove_version(version)
|
|
96
|
+
dir = version_dir(version)
|
|
97
|
+
dir.rmtree if dir.exist?
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def xdg_config_home
|
|
103
|
+
env = ENV["XDG_CONFIG_HOME"]
|
|
104
|
+
return nil if env.nil? || env.empty?
|
|
105
|
+
|
|
106
|
+
env
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Ucd
|
|
7
|
+
# Single source of truth for UCD version selection.
|
|
8
|
+
#
|
|
9
|
+
# Wraps `lib/fontisan/config/ucd.yml`. Loads the YAML once at first
|
|
10
|
+
# access and memoizes. All other Ucd::* classes resolve versions,
|
|
11
|
+
# URLs, and known-version validation through this module.
|
|
12
|
+
module Config
|
|
13
|
+
CONFIG_PATH = File.expand_path("../config/ucd.yml", __dir__)
|
|
14
|
+
private_constant :CONFIG_PATH
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# The version Fontisan uses by default for auto-download and
|
|
18
|
+
# `fontisan ucd download` (no args). String like "17.0.0".
|
|
19
|
+
def default_version
|
|
20
|
+
data[:default_version]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Array of version strings this Fontisan release recognizes.
|
|
24
|
+
# Used by VersionResolver to reject unknown versions early.
|
|
25
|
+
def known_versions
|
|
26
|
+
data[:known_versions]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Base URL for fetching UCDXML artifacts.
|
|
30
|
+
def base_url
|
|
31
|
+
data[:base_url]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Listing URL for `--latest` probing.
|
|
35
|
+
def listing_url
|
|
36
|
+
data[:listing_url]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Full URL to the UCDXML flat zip for a given version.
|
|
40
|
+
# @param version [String] e.g. "17.0.0"
|
|
41
|
+
# @return [String]
|
|
42
|
+
def ucdxml_url_for(version)
|
|
43
|
+
"#{base_url}/#{version}/ucdxml/ucd.all.flat.zip"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# True if the version appears in `known_versions`.
|
|
47
|
+
def known?(version)
|
|
48
|
+
known_versions.include?(version)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def data
|
|
54
|
+
@data ||= YAML.load_file(CONFIG_PATH).transform_keys(&:to_sym)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "tempfile"
|
|
6
|
+
require "zip"
|
|
7
|
+
|
|
8
|
+
module Fontisan
|
|
9
|
+
module Ucd
|
|
10
|
+
# Fetches UCDXML zips from unicode.org and unpacks them into the cache.
|
|
11
|
+
#
|
|
12
|
+
# Single entry point: `Downloader.download(version, force:)`.
|
|
13
|
+
# Idempotent unless `force: true`. Returns the path to the unpacked
|
|
14
|
+
# `ucd.all.flat.xml`.
|
|
15
|
+
module Downloader
|
|
16
|
+
UCDXML_ZIP_ENTRY = "ucd.all.flat.xml"
|
|
17
|
+
private_constant :UCDXML_ZIP_ENTRY
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
# Download and unpack UCDXML for `version`.
|
|
21
|
+
#
|
|
22
|
+
# @param version [String] e.g. "17.0.0"
|
|
23
|
+
# @param force [Boolean] if false and cache already has the file,
|
|
24
|
+
# return the existing path without re-fetching.
|
|
25
|
+
# @return [Pathname] path to the unpacked ucd.all.flat.xml
|
|
26
|
+
# @raise [DownloadError] on HTTP failure or zip extraction failure
|
|
27
|
+
def download(version, force: false)
|
|
28
|
+
target = CacheManager.ucdxml_path(version)
|
|
29
|
+
return target if target.exist? && !force
|
|
30
|
+
|
|
31
|
+
CacheManager.ensure_version_dir!(version)
|
|
32
|
+
zip_data = fetch_zip(version)
|
|
33
|
+
extract_xml(zip_data, target)
|
|
34
|
+
target
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def fetch_zip(version)
|
|
40
|
+
uri = URI(Config.ucdxml_url_for(version))
|
|
41
|
+
response = Net::HTTP.get_response(uri)
|
|
42
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
43
|
+
raise DownloadError,
|
|
44
|
+
"GET #{uri} returned HTTP #{response.code}: #{response.message}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
body = response.body
|
|
48
|
+
if body.nil? || body.empty?
|
|
49
|
+
raise DownloadError, "GET #{uri} returned an empty body"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
body
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
raise e if e.is_a?(DownloadError)
|
|
55
|
+
|
|
56
|
+
raise DownloadError, "Failed to fetch #{uri}: #{e.message}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def extract_xml(zip_data, target)
|
|
60
|
+
Tempfile.create(["fontisan-ucd", ".zip"]) do |tmp|
|
|
61
|
+
tmp.binmode
|
|
62
|
+
tmp.write(zip_data)
|
|
63
|
+
tmp.flush
|
|
64
|
+
tmp.rewind
|
|
65
|
+
|
|
66
|
+
write_xml_entry(tmp.path, target)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def write_xml_entry(zip_path, target)
|
|
71
|
+
Zip::File.open(zip_path) do |zip|
|
|
72
|
+
entry = zip.find_entry(UCDXML_ZIP_ENTRY) ||
|
|
73
|
+
zip.glob("#{UCDXML_ZIP_ENTRY}*", include_directories: false).first
|
|
74
|
+
unless entry
|
|
75
|
+
raise DownloadError,
|
|
76
|
+
"UCDXML zip did not contain #{UCDXML_ZIP_ENTRY.inspect}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Atomic-ish: write to .part then rename.
|
|
80
|
+
partial = target.sub_ext(".xml.part")
|
|
81
|
+
zip.extract(entry, partial.to_s) { true } # overwrite
|
|
82
|
+
File.rename(partial.to_s, target.to_s)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|