fontisan 0.2.22 → 0.3.0

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.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/.rubocop_todo.yml +93 -17
  4. data/CHANGELOG.md +12 -2
  5. data/README.adoc +6 -210
  6. data/fontisan.gemspec +48 -0
  7. data/lib/fontisan/cldr/unicode_set_parser.rb +23 -6
  8. data/lib/fontisan/cldr/version_resolver.rb +1 -1
  9. data/lib/fontisan/cli.rb +4 -168
  10. data/lib/fontisan/commands.rb +0 -3
  11. data/lib/fontisan/formatters/text_formatter.rb +0 -6
  12. data/lib/fontisan/formatters.rb +0 -3
  13. data/lib/fontisan/hints.rb +6 -3
  14. data/lib/fontisan/models.rb +4 -4
  15. data/lib/fontisan/pipeline/strategies.rb +4 -2
  16. data/lib/fontisan/pipeline.rb +2 -1
  17. data/lib/fontisan/stitcher/selector/codepoints.rb +29 -0
  18. data/lib/fontisan/stitcher/selector/gid.rb +25 -0
  19. data/lib/fontisan/stitcher/selector/range.rb +30 -0
  20. data/lib/fontisan/stitcher/selector.rb +26 -0
  21. data/lib/fontisan/stitcher/source.rb +97 -0
  22. data/lib/fontisan/stitcher.rb +182 -0
  23. data/lib/fontisan/stitcher_cli.rb +69 -0
  24. data/lib/fontisan/tables/cff.rb +2 -1
  25. data/lib/fontisan/tables.rb +2 -1
  26. data/lib/fontisan/ufo/anchor.rb +17 -0
  27. data/lib/fontisan/ufo/cli.rb +85 -0
  28. data/lib/fontisan/ufo/compile/base_compiler.rb +81 -0
  29. data/lib/fontisan/ufo/compile/cff.rb +224 -0
  30. data/lib/fontisan/ufo/compile/cmap.rb +129 -0
  31. data/lib/fontisan/ufo/compile/filters/cubic_to_quadratic.rb +174 -0
  32. data/lib/fontisan/ufo/compile/filters/decompose_components.rb +33 -0
  33. data/lib/fontisan/ufo/compile/filters/flatten_components.rb +22 -0
  34. data/lib/fontisan/ufo/compile/filters/reverse_contour_direction.rb +27 -0
  35. data/lib/fontisan/ufo/compile/filters.rb +57 -0
  36. data/lib/fontisan/ufo/compile/glyf_loca.rb +145 -0
  37. data/lib/fontisan/ufo/compile/head.rb +98 -0
  38. data/lib/fontisan/ufo/compile/hhea.rb +36 -0
  39. data/lib/fontisan/ufo/compile/hmtx.rb +27 -0
  40. data/lib/fontisan/ufo/compile/maxp.rb +57 -0
  41. data/lib/fontisan/ufo/compile/name.rb +79 -0
  42. data/lib/fontisan/ufo/compile/os2.rb +81 -0
  43. data/lib/fontisan/ufo/compile/otf_compiler.rb +43 -0
  44. data/lib/fontisan/ufo/compile/post.rb +32 -0
  45. data/lib/fontisan/ufo/compile/ttf_compiler.rb +69 -0
  46. data/lib/fontisan/ufo/compile.rb +48 -0
  47. data/lib/fontisan/ufo/component.rb +18 -0
  48. data/lib/fontisan/ufo/contour.rb +29 -0
  49. data/lib/fontisan/ufo/convert/from_bin_data.rb +246 -0
  50. data/lib/fontisan/ufo/convert.rb +18 -0
  51. data/lib/fontisan/ufo/data_set.rb +21 -0
  52. data/lib/fontisan/ufo/features.rb +17 -0
  53. data/lib/fontisan/ufo/font.rb +61 -0
  54. data/lib/fontisan/ufo/glyph.rb +421 -0
  55. data/lib/fontisan/ufo/guideline.rb +19 -0
  56. data/lib/fontisan/ufo/image.rb +16 -0
  57. data/lib/fontisan/ufo/image_set.rb +19 -0
  58. data/lib/fontisan/ufo/info.rb +79 -0
  59. data/lib/fontisan/ufo/kerning.rb +32 -0
  60. data/lib/fontisan/ufo/layer.rb +38 -0
  61. data/lib/fontisan/ufo/layer_set.rb +37 -0
  62. data/lib/fontisan/ufo/lib.rb +24 -0
  63. data/lib/fontisan/ufo/plist.rb +118 -0
  64. data/lib/fontisan/ufo/point.rb +38 -0
  65. data/lib/fontisan/ufo/reader.rb +144 -0
  66. data/lib/fontisan/ufo/transformation.rb +39 -0
  67. data/lib/fontisan/ufo/writer.rb +115 -0
  68. data/lib/fontisan/ufo.rb +44 -0
  69. data/lib/fontisan/version.rb +1 -1
  70. data/lib/fontisan.rb +3 -3
  71. metadata +57 -70
  72. data/lib/fontisan/audit/codepoint_range_coalescer.rb +0 -41
  73. data/lib/fontisan/audit/context.rb +0 -122
  74. data/lib/fontisan/audit/differ.rb +0 -124
  75. data/lib/fontisan/audit/extractors/aggregations.rb +0 -54
  76. data/lib/fontisan/audit/extractors/base.rb +0 -26
  77. data/lib/fontisan/audit/extractors/color_capabilities.rb +0 -141
  78. data/lib/fontisan/audit/extractors/coverage.rb +0 -48
  79. data/lib/fontisan/audit/extractors/hinting.rb +0 -197
  80. data/lib/fontisan/audit/extractors/identity.rb +0 -52
  81. data/lib/fontisan/audit/extractors/language_coverage.rb +0 -37
  82. data/lib/fontisan/audit/extractors/licensing.rb +0 -79
  83. data/lib/fontisan/audit/extractors/metrics.rb +0 -103
  84. data/lib/fontisan/audit/extractors/opentype_layout.rb +0 -69
  85. data/lib/fontisan/audit/extractors/provenance.rb +0 -29
  86. data/lib/fontisan/audit/extractors/style.rb +0 -32
  87. data/lib/fontisan/audit/extractors/variation_detail.rb +0 -99
  88. data/lib/fontisan/audit/extractors.rb +0 -27
  89. data/lib/fontisan/audit/library_aggregator.rb +0 -83
  90. data/lib/fontisan/audit/library_auditor.rb +0 -90
  91. data/lib/fontisan/audit/registry.rb +0 -60
  92. data/lib/fontisan/audit/style_extractor.rb +0 -80
  93. data/lib/fontisan/audit.rb +0 -20
  94. data/lib/fontisan/cli/ucd_cli.rb +0 -97
  95. data/lib/fontisan/commands/audit_command.rb +0 -123
  96. data/lib/fontisan/commands/audit_compare_command.rb +0 -66
  97. data/lib/fontisan/commands/audit_library_command.rb +0 -46
  98. data/lib/fontisan/config/ucd.yml +0 -23
  99. data/lib/fontisan/formatters/audit_diff_text_renderer.rb +0 -122
  100. data/lib/fontisan/formatters/audit_text_renderer.rb +0 -324
  101. data/lib/fontisan/formatters/library_summary_text_renderer.rb +0 -99
  102. data/lib/fontisan/models/audit/audit_axis.rb +0 -30
  103. data/lib/fontisan/models/audit/audit_block.rb +0 -32
  104. data/lib/fontisan/models/audit/audit_diff.rb +0 -77
  105. data/lib/fontisan/models/audit/audit_report.rb +0 -153
  106. data/lib/fontisan/models/audit/codepoint_range.rb +0 -40
  107. data/lib/fontisan/models/audit/codepoint_set_diff.rb +0 -34
  108. data/lib/fontisan/models/audit/color_capabilities.rb +0 -93
  109. data/lib/fontisan/models/audit/duplicate_group.rb +0 -23
  110. data/lib/fontisan/models/audit/embedding_type.rb +0 -76
  111. data/lib/fontisan/models/audit/field_change.rb +0 -28
  112. data/lib/fontisan/models/audit/fs_selection_flags.rb +0 -61
  113. data/lib/fontisan/models/audit/gasp_range.rb +0 -63
  114. data/lib/fontisan/models/audit/hinting.rb +0 -93
  115. data/lib/fontisan/models/audit/library_summary.rb +0 -40
  116. data/lib/fontisan/models/audit/licensing.rb +0 -48
  117. data/lib/fontisan/models/audit/metrics.rb +0 -111
  118. data/lib/fontisan/models/audit/named_instance.rb +0 -41
  119. data/lib/fontisan/models/audit/opentype_layout.rb +0 -40
  120. data/lib/fontisan/models/audit/script_coverage_row.rb +0 -26
  121. data/lib/fontisan/models/audit/script_features.rb +0 -28
  122. data/lib/fontisan/models/audit/variation_detail.rb +0 -44
  123. data/lib/fontisan/models/audit.rb +0 -33
  124. data/lib/fontisan/models/ucd/ucd.rb +0 -38
  125. data/lib/fontisan/models/ucd/ucd_char.rb +0 -67
  126. data/lib/fontisan/models/ucd.rb +0 -19
  127. data/lib/fontisan/ucd/aggregator.rb +0 -73
  128. data/lib/fontisan/ucd/cache_manager.rb +0 -111
  129. data/lib/fontisan/ucd/config.rb +0 -59
  130. data/lib/fontisan/ucd/download_error.rb +0 -9
  131. data/lib/fontisan/ucd/downloader.rb +0 -88
  132. data/lib/fontisan/ucd/error.rb +0 -8
  133. data/lib/fontisan/ucd/index.rb +0 -103
  134. data/lib/fontisan/ucd/index_builder.rb +0 -107
  135. data/lib/fontisan/ucd/range_entry.rb +0 -56
  136. data/lib/fontisan/ucd/unknown_version_error.rb +0 -9
  137. data/lib/fontisan/ucd/version_resolver.rb +0 -79
  138. data/lib/fontisan/ucd.rb +0 -23
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fontisan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.22
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-06-29 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: base64
@@ -262,29 +263,8 @@ files:
262
263
  - docs/public/web-app-manifest-512x512.png
263
264
  - docs/scripts/post-build.mjs
264
265
  - exe/fontisan
266
+ - fontisan.gemspec
265
267
  - lib/fontisan.rb
266
- - lib/fontisan/audit.rb
267
- - lib/fontisan/audit/codepoint_range_coalescer.rb
268
- - lib/fontisan/audit/context.rb
269
- - lib/fontisan/audit/differ.rb
270
- - lib/fontisan/audit/extractors.rb
271
- - lib/fontisan/audit/extractors/aggregations.rb
272
- - lib/fontisan/audit/extractors/base.rb
273
- - lib/fontisan/audit/extractors/color_capabilities.rb
274
- - lib/fontisan/audit/extractors/coverage.rb
275
- - lib/fontisan/audit/extractors/hinting.rb
276
- - lib/fontisan/audit/extractors/identity.rb
277
- - lib/fontisan/audit/extractors/language_coverage.rb
278
- - lib/fontisan/audit/extractors/licensing.rb
279
- - lib/fontisan/audit/extractors/metrics.rb
280
- - lib/fontisan/audit/extractors/opentype_layout.rb
281
- - lib/fontisan/audit/extractors/provenance.rb
282
- - lib/fontisan/audit/extractors/style.rb
283
- - lib/fontisan/audit/extractors/variation_detail.rb
284
- - lib/fontisan/audit/library_aggregator.rb
285
- - lib/fontisan/audit/library_auditor.rb
286
- - lib/fontisan/audit/registry.rb
287
- - lib/fontisan/audit/style_extractor.rb
288
268
  - lib/fontisan/base_collection.rb
289
269
  - lib/fontisan/binary.rb
290
270
  - lib/fontisan/binary/base_record.rb
@@ -303,7 +283,6 @@ files:
303
283
  - lib/fontisan/cldr/version_resolver.rb
304
284
  - lib/fontisan/cli.rb
305
285
  - lib/fontisan/cli/cldr_cli.rb
306
- - lib/fontisan/cli/ucd_cli.rb
307
286
  - lib/fontisan/collection.rb
308
287
  - lib/fontisan/collection/builder.rb
309
288
  - lib/fontisan/collection/dfont_builder.rb
@@ -313,9 +292,6 @@ files:
313
292
  - lib/fontisan/collection/table_deduplicator.rb
314
293
  - lib/fontisan/collection/writer.rb
315
294
  - lib/fontisan/commands.rb
316
- - lib/fontisan/commands/audit_command.rb
317
- - lib/fontisan/commands/audit_compare_command.rb
318
- - lib/fontisan/commands/audit_library_command.rb
319
295
  - lib/fontisan/commands/base_command.rb
320
296
  - lib/fontisan/commands/convert_command.rb
321
297
  - lib/fontisan/commands/dump_table_command.rb
@@ -342,7 +318,6 @@ files:
342
318
  - lib/fontisan/config/scripts.yml
343
319
  - lib/fontisan/config/subset_profiles.yml
344
320
  - lib/fontisan/config/svg_settings.yml
345
- - lib/fontisan/config/ucd.yml
346
321
  - lib/fontisan/config/variable_settings.yml
347
322
  - lib/fontisan/config/woff2_settings.yml
348
323
  - lib/fontisan/constants.rb
@@ -379,9 +354,6 @@ files:
379
354
  - lib/fontisan/font_loader.rb
380
355
  - lib/fontisan/font_writer.rb
381
356
  - lib/fontisan/formatters.rb
382
- - lib/fontisan/formatters/audit_diff_text_renderer.rb
383
- - lib/fontisan/formatters/audit_text_renderer.rb
384
- - lib/fontisan/formatters/library_summary_text_renderer.rb
385
357
  - lib/fontisan/formatters/text_formatter.rb
386
358
  - lib/fontisan/glyph_accessor.rb
387
359
  - lib/fontisan/hints.rb
@@ -397,28 +369,6 @@ files:
397
369
  - lib/fontisan/metrics_calculator.rb
398
370
  - lib/fontisan/models.rb
399
371
  - lib/fontisan/models/all_scripts_features_info.rb
400
- - lib/fontisan/models/audit.rb
401
- - lib/fontisan/models/audit/audit_axis.rb
402
- - lib/fontisan/models/audit/audit_block.rb
403
- - lib/fontisan/models/audit/audit_diff.rb
404
- - lib/fontisan/models/audit/audit_report.rb
405
- - lib/fontisan/models/audit/codepoint_range.rb
406
- - lib/fontisan/models/audit/codepoint_set_diff.rb
407
- - lib/fontisan/models/audit/color_capabilities.rb
408
- - lib/fontisan/models/audit/duplicate_group.rb
409
- - lib/fontisan/models/audit/embedding_type.rb
410
- - lib/fontisan/models/audit/field_change.rb
411
- - lib/fontisan/models/audit/fs_selection_flags.rb
412
- - lib/fontisan/models/audit/gasp_range.rb
413
- - lib/fontisan/models/audit/hinting.rb
414
- - lib/fontisan/models/audit/library_summary.rb
415
- - lib/fontisan/models/audit/licensing.rb
416
- - lib/fontisan/models/audit/metrics.rb
417
- - lib/fontisan/models/audit/named_instance.rb
418
- - lib/fontisan/models/audit/opentype_layout.rb
419
- - lib/fontisan/models/audit/script_coverage_row.rb
420
- - lib/fontisan/models/audit/script_features.rb
421
- - lib/fontisan/models/audit/variation_detail.rb
422
372
  - lib/fontisan/models/bitmap_glyph.rb
423
373
  - lib/fontisan/models/bitmap_strike.rb
424
374
  - lib/fontisan/models/cldr.rb
@@ -456,9 +406,6 @@ files:
456
406
  - lib/fontisan/models/ttx/tables/os2_table.rb
457
407
  - lib/fontisan/models/ttx/tables/post_table.rb
458
408
  - lib/fontisan/models/ttx/ttfont.rb
459
- - lib/fontisan/models/ucd.rb
460
- - lib/fontisan/models/ucd/ucd.rb
461
- - lib/fontisan/models/ucd/ucd_char.rb
462
409
  - lib/fontisan/models/unicode_mappings.rb
463
410
  - lib/fontisan/models/validation_report.rb
464
411
  - lib/fontisan/models/variable_font_info.rb
@@ -488,6 +435,13 @@ files:
488
435
  - lib/fontisan/pipeline/variation_resolver.rb
489
436
  - lib/fontisan/sfnt_font.rb
490
437
  - lib/fontisan/sfnt_table.rb
438
+ - lib/fontisan/stitcher.rb
439
+ - lib/fontisan/stitcher/selector.rb
440
+ - lib/fontisan/stitcher/selector/codepoints.rb
441
+ - lib/fontisan/stitcher/selector/gid.rb
442
+ - lib/fontisan/stitcher/selector/range.rb
443
+ - lib/fontisan/stitcher/source.rb
444
+ - lib/fontisan/stitcher_cli.rb
491
445
  - lib/fontisan/subset.rb
492
446
  - lib/fontisan/subset/builder.rb
493
447
  - lib/fontisan/subset/glyph_mapping.rb
@@ -597,18 +551,49 @@ files:
597
551
  - lib/fontisan/type1/ttf_to_type1_converter.rb
598
552
  - lib/fontisan/type1/upm_scaler.rb
599
553
  - lib/fontisan/type1_font.rb
600
- - lib/fontisan/ucd.rb
601
- - lib/fontisan/ucd/aggregator.rb
602
- - lib/fontisan/ucd/cache_manager.rb
603
- - lib/fontisan/ucd/config.rb
604
- - lib/fontisan/ucd/download_error.rb
605
- - lib/fontisan/ucd/downloader.rb
606
- - lib/fontisan/ucd/error.rb
607
- - lib/fontisan/ucd/index.rb
608
- - lib/fontisan/ucd/index_builder.rb
609
- - lib/fontisan/ucd/range_entry.rb
610
- - lib/fontisan/ucd/unknown_version_error.rb
611
- - lib/fontisan/ucd/version_resolver.rb
554
+ - lib/fontisan/ufo.rb
555
+ - lib/fontisan/ufo/anchor.rb
556
+ - lib/fontisan/ufo/cli.rb
557
+ - lib/fontisan/ufo/compile.rb
558
+ - lib/fontisan/ufo/compile/base_compiler.rb
559
+ - lib/fontisan/ufo/compile/cff.rb
560
+ - lib/fontisan/ufo/compile/cmap.rb
561
+ - lib/fontisan/ufo/compile/filters.rb
562
+ - lib/fontisan/ufo/compile/filters/cubic_to_quadratic.rb
563
+ - lib/fontisan/ufo/compile/filters/decompose_components.rb
564
+ - lib/fontisan/ufo/compile/filters/flatten_components.rb
565
+ - lib/fontisan/ufo/compile/filters/reverse_contour_direction.rb
566
+ - lib/fontisan/ufo/compile/glyf_loca.rb
567
+ - lib/fontisan/ufo/compile/head.rb
568
+ - lib/fontisan/ufo/compile/hhea.rb
569
+ - lib/fontisan/ufo/compile/hmtx.rb
570
+ - lib/fontisan/ufo/compile/maxp.rb
571
+ - lib/fontisan/ufo/compile/name.rb
572
+ - lib/fontisan/ufo/compile/os2.rb
573
+ - lib/fontisan/ufo/compile/otf_compiler.rb
574
+ - lib/fontisan/ufo/compile/post.rb
575
+ - lib/fontisan/ufo/compile/ttf_compiler.rb
576
+ - lib/fontisan/ufo/component.rb
577
+ - lib/fontisan/ufo/contour.rb
578
+ - lib/fontisan/ufo/convert.rb
579
+ - lib/fontisan/ufo/convert/from_bin_data.rb
580
+ - lib/fontisan/ufo/data_set.rb
581
+ - lib/fontisan/ufo/features.rb
582
+ - lib/fontisan/ufo/font.rb
583
+ - lib/fontisan/ufo/glyph.rb
584
+ - lib/fontisan/ufo/guideline.rb
585
+ - lib/fontisan/ufo/image.rb
586
+ - lib/fontisan/ufo/image_set.rb
587
+ - lib/fontisan/ufo/info.rb
588
+ - lib/fontisan/ufo/kerning.rb
589
+ - lib/fontisan/ufo/layer.rb
590
+ - lib/fontisan/ufo/layer_set.rb
591
+ - lib/fontisan/ufo/lib.rb
592
+ - lib/fontisan/ufo/plist.rb
593
+ - lib/fontisan/ufo/point.rb
594
+ - lib/fontisan/ufo/reader.rb
595
+ - lib/fontisan/ufo/transformation.rb
596
+ - lib/fontisan/ufo/writer.rb
612
597
  - lib/fontisan/utilities.rb
613
598
  - lib/fontisan/utilities/brotli_wrapper.rb
614
599
  - lib/fontisan/utilities/checksum_calculator.rb
@@ -675,6 +660,7 @@ metadata:
675
660
  source_code_uri: https://github.com/fontist/fontisan
676
661
  changelog_uri: https://github.com/fontist/fontisan/blob/main/CHANGELOG.md
677
662
  rubygems_mfa_required: 'true'
663
+ post_install_message:
678
664
  rdoc_options: []
679
665
  require_paths:
680
666
  - lib
@@ -689,7 +675,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
689
675
  - !ruby/object:Gem::Version
690
676
  version: '0'
691
677
  requirements: []
692
- rubygems_version: 3.6.9
678
+ rubygems_version: 3.5.22
679
+ signing_key:
693
680
  specification_version: 4
694
681
  summary: Font analysis tools and utilities for OpenType fonts
695
682
  test_files: []
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fontisan
4
- module Audit
5
- # Coalesces a flat codepoint list into contiguous {Models::Audit::CodepointRange}s.
6
- #
7
- # Single static call site, deterministic output. Used by the Coverage
8
- # extractor to produce the compact range view that is the default
9
- # AuditReport shape.
10
- module CodepointRangeCoalescer
11
- module_function
12
-
13
- # @param codepoints [Enumerable<Integer>] any enumeration of integers
14
- # @return [Array<Models::Audit::CodepointRange>] contiguous, sorted
15
- def call(codepoints)
16
- return [] if codepoints.nil? || codepoints.empty?
17
-
18
- sorted = codepoints.sort.uniq
19
- ranges = []
20
- range_start = sorted[0]
21
- prev = sorted[0]
22
-
23
- sorted[1..].each do |cp|
24
- next if cp == prev # defensive: .uniq already handles this
25
-
26
- if cp == prev + 1
27
- prev = cp
28
- else
29
- ranges << Models::Audit::CodepointRange.new(first_cp: range_start,
30
- last_cp: prev)
31
- range_start = cp
32
- prev = cp
33
- end
34
- end
35
- ranges << Models::Audit::CodepointRange.new(first_cp: range_start,
36
- last_cp: prev)
37
- ranges
38
- end
39
- end
40
- end
41
- end
@@ -1,122 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fontisan
4
- module Audit
5
- # Value object carrying everything an extractor needs to do its job.
6
- #
7
- # Extractors never reach back into AuditCommand state — they read
8
- # exclusively from the Context. Shared derived data (codepoints,
9
- # UCD indices, source format) is memoized here so multiple
10
- # extractors don't recompute it.
11
- class Context
12
- attr_reader :font, :font_path, :font_index, :num_fonts_in_source,
13
- :options
14
-
15
- def initialize(font:, font_path:, font_index:, num_fonts_in_source:,
16
- options:)
17
- @font = font
18
- @font_path = font_path
19
- @font_index = font_index
20
- @num_fonts_in_source = num_fonts_in_source
21
- @options = options
22
- end
23
-
24
- def codepoints
25
- @codepoints ||= extract_codepoints
26
- end
27
-
28
- def ucd
29
- @ucd ||= resolve_ucd
30
- end
31
-
32
- def cldr
33
- return nil unless @options[:with_language_coverage]
34
-
35
- @cldr ||= resolve_cldr
36
- end
37
-
38
- def source_format
39
- @source_format ||= FontLoader.detect_format(@font_path)&.to_s
40
- end
41
-
42
- def all_codepoints?
43
- @options[:all_codepoints] == true
44
- end
45
-
46
- private
47
-
48
- def extract_codepoints
49
- return [] unless @font.has_table?(Constants::CMAP_TAG)
50
-
51
- @font.table(Constants::CMAP_TAG).unicode_mappings.keys
52
- end
53
-
54
- def resolve_ucd
55
- version = Ucd::VersionResolver.resolve(@options[:ucd_version])
56
-
57
- with_local_indices(version) do |blocks_path, scripts_path|
58
- {
59
- version: version,
60
- blocks_index: Ucd::Index.load(blocks_path),
61
- scripts_index: Ucd::Index.load(scripts_path),
62
- warning: nil,
63
- }
64
- end
65
- rescue Ucd::UnknownVersionError => e
66
- { version: nil, blocks_index: nil, scripts_index: nil,
67
- warning: "UCD version rejected: #{e.message}" }
68
- rescue StandardError => e
69
- version_ref = @ucd&.fetch(:version, nil)
70
- {
71
- version: version_ref,
72
- blocks_index: nil,
73
- scripts_index: nil,
74
- warning: "UCD unavailable for version #{version_ref}: #{e.message}",
75
- }
76
- end
77
-
78
- def with_local_indices(version)
79
- unless Ucd::CacheManager.cached?(version)
80
- Ucd::Downloader.download(version)
81
- end
82
- unless Ucd::CacheManager.blocks_index_path(version).exist?
83
- Ucd::IndexBuilder.build(version)
84
- end
85
- yield Ucd::CacheManager.blocks_index_path(version),
86
- Ucd::CacheManager.scripts_index_path(version)
87
- end
88
-
89
- def resolve_cldr
90
- version = Cldr::VersionResolver.resolve(@options[:cldr_version])
91
-
92
- with_local_languages_index(version) do |index_path|
93
- {
94
- version: version,
95
- index: Cldr::Index.load(index_path),
96
- warning: nil,
97
- }
98
- end
99
- rescue Cldr::UnknownVersionError => e
100
- { version: nil, index: nil,
101
- warning: "CLDR version rejected: #{e.message}" }
102
- rescue StandardError => e
103
- version_ref = @cldr&.fetch(:version, nil)
104
- {
105
- version: version_ref,
106
- index: nil,
107
- warning: "CLDR unavailable for version #{version_ref}: #{e.message}",
108
- }
109
- end
110
-
111
- def with_local_languages_index(version)
112
- unless Cldr::CacheManager.cached?(version)
113
- Cldr::Downloader.download(version)
114
- end
115
- unless Cldr::CacheManager.languages_index_path(version).exist?
116
- Cldr::IndexBuilder.build(version)
117
- end
118
- yield Cldr::CacheManager.languages_index_path(version)
119
- end
120
- end
121
- end
122
- end
@@ -1,124 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fontisan
4
- module Audit
5
- # Computes an {Models::Audit::AuditDiff} between two AuditReports.
6
- #
7
- # Pure: no I/O, no font parsing. Both reports must already be built
8
- # (Commands::AuditCompareCommand handles loading reports from disk
9
- # or auditing fresh fonts before invoking the differ).
10
- #
11
- # Comparison shape:
12
- # - Scalar fields: one FieldChange per differing field.
13
- # - Codepoint coverage: CodepointSetDiff built from the cmap range
14
- # lists (expanded to integer sets for set arithmetic, then
15
- # re-coalesced to ranges for output).
16
- # - Structural inventories (features, scripts, blocks, languages):
17
- # simple array set-diffs.
18
- class Differ
19
- # Scalar AuditReport fields compared field-by-field. Excludes
20
- # generated_at / source_sha256 / source_file (per-report identity),
21
- # codepoints / codepoint_ranges (handled via CodepointSetDiff),
22
- # and nested models (surfaced via structural add/remove lists).
23
- COMPARED_FIELDS = %i[
24
- family_name subfamily_name full_name postscript_name version
25
- font_revision weight_class width_class italic bold panose
26
- total_codepoints total_glyphs ucd_version cldr_version
27
- ].freeze
28
-
29
- def initialize(left_report, right_report)
30
- @left = left_report
31
- @right = right_report
32
- end
33
-
34
- # @return [Models::Audit::AuditDiff]
35
- def diff
36
- Models::Audit::AuditDiff.new(
37
- left_source: @left.source_file,
38
- right_source: @right.source_file,
39
- field_changes: field_changes,
40
- codepoints: codepoint_diff,
41
- added_features: set_diff(features(@right), features(@left)),
42
- removed_features: set_diff(features(@left), features(@right)),
43
- added_scripts: set_diff(scripts(@right), scripts(@left)),
44
- removed_scripts: set_diff(scripts(@left), scripts(@right)),
45
- added_blocks: set_diff(blocks(@right), blocks(@left)),
46
- removed_blocks: set_diff(blocks(@left), blocks(@right)),
47
- added_languages: set_diff(languages(@right), languages(@left)),
48
- removed_languages: set_diff(languages(@left), languages(@right)),
49
- )
50
- end
51
-
52
- private
53
-
54
- def field_changes
55
- COMPARED_FIELDS.filter_map do |field|
56
- left_val = @left.public_send(field)
57
- right_val = @right.public_send(field)
58
- next if left_val == right_val
59
-
60
- Models::Audit::FieldChange.new(
61
- field: field.to_s,
62
- left: serialize_value(left_val),
63
- right: serialize_value(right_val),
64
- )
65
- end
66
- end
67
-
68
- def codepoint_diff
69
- left_set = codepoints_from_ranges(@left)
70
- right_set = codepoints_from_ranges(@right)
71
- added = right_set - left_set
72
- removed = left_set - right_set
73
- unchanged = left_set & right_set
74
-
75
- Models::Audit::CodepointSetDiff.new(
76
- added: CodepointRangeCoalescer.call(added.to_a),
77
- removed: CodepointRangeCoalescer.call(removed.to_a),
78
- added_count: added.size,
79
- removed_count: removed.size,
80
- unchanged_count: unchanged.size,
81
- )
82
- end
83
-
84
- # Expand a report's compact codepoint range list into a Set<Integer>.
85
- # Works for both default reports (range list populated) and
86
- # --all-codepoints reports (range list is also populated).
87
- def codepoints_from_ranges(report)
88
- ranges = report.codepoint_ranges || []
89
- ranges.each_with_object(Set.new) do |range, set|
90
- (range.first_cp..range.last_cp).each { |cp| set << cp }
91
- end
92
- end
93
-
94
- def features(report)
95
- report.opentype_layout&.features || []
96
- end
97
-
98
- def scripts(report)
99
- report.opentype_layout&.scripts || []
100
- end
101
-
102
- def blocks(report)
103
- (report.blocks || []).map(&:name)
104
- end
105
-
106
- def languages(report)
107
- (report.language_coverage || []).map(&:language)
108
- end
109
-
110
- def set_diff(minuend, subtrahend)
111
- (Array(minuend) - Array(subtrahend)).sort
112
- end
113
-
114
- def serialize_value(value)
115
- case value
116
- when nil then ""
117
- when String, Integer, Float then value.to_s
118
- when true, false then value.to_s
119
- else value.to_yaml
120
- end
121
- end
122
- end
123
- end
124
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fontisan
4
- module Audit
5
- module Extractors
6
- # Aggregation fields: UCD block/script coverage.
7
- #
8
- # Returned fields:
9
- # ucd_version, blocks, unicode_scripts
10
- #
11
- # OpenType script/feature inventory lives in {Extractors::OpenTypeLayout}
12
- # (MECE: this extractor is UCD-driven, that one is SFNT-table-driven).
13
- class Aggregations < Base
14
- def extract(context)
15
- ucd = context.ucd
16
- ucd_aggregations(context.codepoints, ucd)
17
- end
18
-
19
- private
20
-
21
- def ucd_aggregations(codepoints, ucd)
22
- return empty_aggregation(ucd) if ucd[:blocks_index].nil?
23
-
24
- blocks_hashes = Ucd::Aggregator.aggregate_blocks(codepoints,
25
- ucd[:blocks_index])
26
- {
27
- ucd_version: ucd[:version],
28
- blocks: blocks_hashes.map { |h| build_audit_block(h) },
29
- unicode_scripts: Ucd::Aggregator.aggregate_scripts(codepoints,
30
- ucd[:scripts_index]),
31
- }
32
- end
33
-
34
- def empty_aggregation(ucd)
35
- { ucd_version: ucd[:version], blocks: [], unicode_scripts: [] }
36
- end
37
-
38
- def build_audit_block(block_hash)
39
- Models::Audit::AuditBlock.new(
40
- name: block_hash[:name],
41
- first_cp: block_hash[:first_cp],
42
- last_cp: block_hash[:last_cp],
43
- range: format("U+%<first>04X-U+%<last>04X",
44
- first: block_hash[:first_cp], last: block_hash[:last_cp]),
45
- total: block_hash[:total],
46
- covered: block_hash[:covered],
47
- fill_ratio: block_hash[:fill_ratio],
48
- complete: block_hash[:complete],
49
- )
50
- end
51
- end
52
- end
53
- end
54
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fontisan
4
- module Audit
5
- module Extractors
6
- # Abstract extractor interface. Subclasses implement `#extract`.
7
- #
8
- # An extractor reads from a Context and returns a hash of fields
9
- # suitable for `Models::Audit::AuditReport.new(**fields)`.
10
- # Returning an empty hash is valid (no-op).
11
- class Base
12
- def extract(context)
13
- raise NotImplementedError,
14
- "#{self.class} must implement #extract"
15
- end
16
-
17
- protected
18
-
19
- # Convenience accessor used by most extractors.
20
- def font(context)
21
- context.font
22
- end
23
- end
24
- end
25
- end
26
- end