fontisan 0.2.6 → 0.2.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ee842928fa05a0dceee00f233fe76f0f392083087807e8d85dc2ab3840b17e4
4
- data.tar.gz: fd136b7b46e1cf21b3bcd9a968fb13cf9abe59bff4b6c73b798db8bee40d7a02
3
+ metadata.gz: 39c9d53cc2e783d606e838535f0fe703a74718bd36b1864a7a5070ae0a4e46f2
4
+ data.tar.gz: a84826024c22f54279c657ac22c66f9d7f6030f4d46158a16d7801559783b521
5
5
  SHA512:
6
- metadata.gz: b0d3626427babba6144519e649ac113673a42d89ff1fde9b2c854d7ec72e8ef3aa4434cb98cc59c6243c0b428607c6ad30b746955afbec9452f2b06fbb31ba54
7
- data.tar.gz: 3d871f07e2591a82081cf1c5f7582d3e731f1d1cf1fc0e416ff0c9216de417046220bd6afd8d482c6c096a2c96639004e567cec74c3ef8b9eac2c9815d2c5738
6
+ metadata.gz: fbb949d64435949177b73be36751fdd3e0a89cfcbfdd3032f2307b825f10148cac330db52807aa2df1558393b55390096266a100e30ca83244b962a6f3e7d5c7
7
+ data.tar.gz: c6c0b91fd3d690d126720b135d99b8cd87b706066e38b84b9b2ce44069c8811d710cc271a7df48c8c8e20dc57c1702798347442872708c756df0323f838b63fe
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-01-05 11:02:40 UTC using RuboCop version 1.81.7.
3
+ # on 2026-01-06 02:07:50 UTC using RuboCop version 1.81.7.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -51,7 +51,7 @@ Layout/ExtraSpacing:
51
51
  - 'spec/integration/color_emoji_fonts_spec.rb'
52
52
  - 'spec/integration/dfont_pack_spec.rb'
53
53
 
54
- # Offense count: 1309
54
+ # Offense count: 1313
55
55
  # This cop supports safe autocorrection (--autocorrect).
56
56
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
57
57
  # URISchemes: http, https
@@ -64,13 +64,14 @@ Layout/LineLength:
64
64
  # SupportedStyles: final_newline, final_blank_line
65
65
  Layout/TrailingEmptyLines:
66
66
  Exclude:
67
- - 'spec/integration/dfont_pack_spec.rb'
67
+ - 'lib/fontisan/collection/shared_logic.rb'
68
68
 
69
- # Offense count: 18
69
+ # Offense count: 19
70
70
  # This cop supports safe autocorrection (--autocorrect).
71
71
  # Configuration parameters: AllowInHeredoc.
72
72
  Layout/TrailingWhitespace:
73
73
  Exclude:
74
+ - 'check_dfont.rb'
74
75
  - 'spec/fontisan/converters/woff2_encoder_integration_spec.rb'
75
76
 
76
77
  # Offense count: 27
@@ -175,12 +176,12 @@ Metrics/BlockLength:
175
176
  Metrics/BlockNesting:
176
177
  Max: 5
177
178
 
178
- # Offense count: 234
179
+ # Offense count: 233
179
180
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
180
181
  Metrics/CyclomaticComplexity:
181
182
  Enabled: false
182
183
 
183
- # Offense count: 706
184
+ # Offense count: 707
184
185
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
185
186
  Metrics/MethodLength:
186
187
  Max: 135
@@ -266,7 +267,7 @@ RSpec/DescribeMethod:
266
267
  - 'spec/fontisan/collection/variable_font_builder_spec.rb'
267
268
  - 'spec/fontisan/converters/woff2_encoder_integration_spec.rb'
268
269
 
269
- # Offense count: 1284
270
+ # Offense count: 1280
270
271
  # Configuration parameters: CountAsOne.
271
272
  RSpec/ExampleLength:
272
273
  Max: 66
@@ -340,11 +341,11 @@ RSpec/MultipleDescribes:
340
341
  - 'spec/fontisan/utils/thread_pool_spec.rb'
341
342
  - 'spec/fontisan/variation/cache_spec.rb'
342
343
 
343
- # Offense count: 1688
344
+ # Offense count: 1697
344
345
  RSpec/MultipleExpectations:
345
- Max: 22
346
+ Max: 19
346
347
 
347
- # Offense count: 148
348
+ # Offense count: 149
348
349
  # Configuration parameters: AllowSubject.
349
350
  RSpec/MultipleMemoizedHelpers:
350
351
  Max: 13
@@ -598,12 +599,13 @@ Style/StringConcatenation:
598
599
  Exclude:
599
600
  - 'spec/fontisan/tables/cbdt_spec.rb'
600
601
 
601
- # Offense count: 53
602
+ # Offense count: 56
602
603
  # This cop supports safe autocorrection (--autocorrect).
603
604
  # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
604
605
  # SupportedStyles: single_quotes, double_quotes
605
606
  Style/StringLiterals:
606
607
  Exclude:
608
+ - 'check_dfont.rb'
607
609
  - 'lib/fontisan/commands/convert_command.rb'
608
610
  - 'lib/fontisan/converters/collection_converter.rb'
609
611
  - 'lib/fontisan/validators/font_book_validator.rb'
data/README.adoc CHANGED
@@ -184,7 +184,7 @@ Family: Noto Serif CJK JP ExtraLight
184
184
  ====
185
185
  [source,shell]
186
186
  ----
187
- $ fontisan info spec/fixtures/fonts/MonaSans/mona-sans-2.0.8/googlefonts/variable/MonaSans[wdth,wght].ttf --brief
187
+ $ fontisan info spec/fixtures/fonts/MonaSans/variable/MonaSans[wdth,wght].ttf --brief
188
188
 
189
189
  Font type: TrueType (Variable)
190
190
  Family: Mona Sans ExtraLight
@@ -1241,7 +1241,7 @@ validator = Fontisan::Validators::OpenTypeValidator.new
1241
1241
  report = validator.validate(font)
1242
1242
 
1243
1243
  # Check individual results
1244
- name_check = report.result_of(:name_version)
1244
+ name_check = report.result_of(:name_validation)
1245
1245
  puts name_check.passed?
1246
1246
  puts name_check.severity
1247
1247
  ----
@@ -1308,7 +1308,7 @@ font = Fontisan::FontLoader.load('font.ttf')
1308
1308
  validator = MyFontValidator.new
1309
1309
  report = validator.validate(font)
1310
1310
 
1311
- # Check results
1311
+ # Check validation results
1312
1312
  puts report.valid? # => true/false
1313
1313
  puts report.status # => "valid", "valid_with_warnings", "invalid"
1314
1314
  puts report.summary.errors # => number of errors
@@ -1576,6 +1576,14 @@ dfont (Apple suitcase)::: Apple proprietary format storing complete Mac font
1576
1576
  suitcase resources in the data fork. Supports any SFNT fonts (TTF, OTF, or
1577
1577
  mixed). Mac OS X specific. File extension: `.dfont`
1578
1578
 
1579
+ Fontist returns the appropriate collection type based on the font data:
1580
+
1581
+ * Examines font data within collection to determine type (TTC vs OTC)
1582
+ * TTC contains fonts with TrueType outlines (glyf table)
1583
+ * OTC contains fonts with CFF outlines (CFF table)
1584
+ * If ANY font in the collection has CFF outlines, use OpenTypeCollection
1585
+ * Only use TrueTypeCollection if ALL fonts have TrueType outlines
1586
+
1579
1587
 
1580
1588
  === Collection compatibility
1581
1589
 
@@ -2076,6 +2084,41 @@ Fontisan can:
2076
2084
  * Importing and generation PNG block ruler layers
2077
2085
 
2078
2086
 
2087
+ == Testing
2088
+
2089
+ === General
2090
+
2091
+ Fontisan has a comprehensive test suite covering all font operations, formats,
2092
+ and features.
2093
+
2094
+ === Running tests
2095
+
2096
+ [source,shell]
2097
+ ----
2098
+ # Run full test suite
2099
+ bundle exec rspec
2100
+
2101
+ # Run with documentation format
2102
+ bundle exec rspec --format documentation
2103
+
2104
+ # Run specific file
2105
+ bundle exec rspec spec/fontisan/tables/maxp_spec.rb
2106
+ ----
2107
+
2108
+ === Test fixtures
2109
+
2110
+ All required font fixtures are automatically downloaded before tests run. The
2111
+ test suite uses a centralized fixture configuration system that ensures all
2112
+ necessary fonts are available.
2113
+
2114
+ [source,shell]
2115
+ ----
2116
+ # Manual fixture management (if needed)
2117
+ bundle exec rake fixtures:download # Download all test fonts
2118
+ bundle exec rake fixtures:clean # Remove downloaded fonts
2119
+ ----
2120
+
2121
+
2079
2122
  == Copyright and license
2080
2123
 
2081
2124
  Copyright https://www.ribose.com[Ribose].
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bindata"
4
4
  require_relative "constants"
5
+ require_relative "collection/shared_logic"
5
6
 
6
7
  module Fontisan
7
8
  # Abstract base class for font collections (TTC/OTC)
@@ -29,6 +30,8 @@ module Fontisan
29
30
  # end
30
31
  # end
31
32
  class BaseCollection < BinData::Record
33
+ include Collection::SharedLogic
34
+
32
35
  endian :big
33
36
 
34
37
  string :tag, length: 4, assert: "ttcf"
@@ -251,8 +254,6 @@ module Fontisan
251
254
  # @param io [IO] Open file handle
252
255
  # @return [TableSharingInfo] Sharing statistics
253
256
  def calculate_table_sharing(io)
254
- require_relative "models/table_sharing_info"
255
-
256
257
  font_class = self.class.font_class
257
258
 
258
259
  # Extract all fonts
@@ -260,37 +261,8 @@ module Fontisan
260
261
  font_class.from_collection(io, offset)
261
262
  end
262
263
 
263
- # Build table hash map (checksum -> size)
264
- table_map = {}
265
- total_table_size = 0
266
-
267
- fonts.each do |font|
268
- font.tables.each do |entry|
269
- key = entry.checksum
270
- size = entry.table_length
271
- table_map[key] ||= size
272
- total_table_size += size
273
- end
274
- end
275
-
276
- # Count unique vs shared
277
- unique_tables = table_map.size
278
- total_tables = fonts.sum { |f| f.tables.length }
279
- shared_tables = total_tables - unique_tables
280
-
281
- # Calculate space saved
282
- unique_size = table_map.values.sum
283
- space_saved = total_table_size - unique_size
284
-
285
- # Calculate sharing percentage
286
- sharing_pct = total_tables.positive? ? (shared_tables.to_f / total_tables * 100).round(2) : 0.0
287
-
288
- Models::TableSharingInfo.new(
289
- shared_tables: shared_tables,
290
- unique_tables: unique_tables,
291
- sharing_percentage: sharing_pct,
292
- space_saved_bytes: space_saved,
293
- )
264
+ # Use shared logic for calculation
265
+ calculate_table_sharing_for_fonts(fonts)
294
266
  end
295
267
  end
296
268
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Collection
5
+ # Shared logic for font collection classes
6
+ #
7
+ # This module provides common functionality for all collection types
8
+ # (TTC, OTC, dfont) to maintain DRY principles.
9
+ module SharedLogic
10
+ # Calculate table sharing statistics
11
+ #
12
+ # Analyzes which tables are shared between fonts and calculates
13
+ # space savings from deduplication.
14
+ #
15
+ # @param fonts [Array<TrueTypeFont, OpenTypeFont>] Array of fonts
16
+ # @return [Models::TableSharingInfo] Sharing statistics
17
+ def calculate_table_sharing_for_fonts(fonts)
18
+ require_relative "../models/table_sharing_info"
19
+
20
+ # Build table hash map (checksum -> size)
21
+ table_map = {}
22
+ total_table_size = 0
23
+
24
+ fonts.each do |font|
25
+ font.tables.each do |entry|
26
+ key = entry.checksum
27
+ size = entry.table_length
28
+ table_map[key] ||= size
29
+ total_table_size += size
30
+ end
31
+ end
32
+
33
+ # Count unique vs shared
34
+ unique_tables = table_map.size
35
+ total_tables = fonts.sum { |f| f.tables.length }
36
+ shared_tables = total_tables - unique_tables
37
+
38
+ # Calculate space saved
39
+ unique_size = table_map.values.sum
40
+ space_saved = total_table_size - unique_size
41
+
42
+ # Calculate sharing percentage
43
+ sharing_pct = total_tables.positive? ? (shared_tables.to_f / total_tables * 100).round(2) : 0.0
44
+
45
+ Models::TableSharingInfo.new(
46
+ shared_tables: shared_tables,
47
+ unique_tables: unique_tables,
48
+ sharing_percentage: sharing_pct,
49
+ space_saved_bytes: space_saved,
50
+ )
51
+ end
52
+ end
53
+ end
54
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "parsers/dfont_parser"
4
4
  require_relative "error"
5
+ require_relative "collection/shared_logic"
5
6
 
6
7
  module Fontisan
7
8
  # DfontCollection represents an Apple dfont suitcase containing multiple fonts
@@ -23,6 +24,8 @@ module Fontisan
23
24
  # fonts.each { |font| puts font.class.name }
24
25
  # end
25
26
  class DfontCollection
27
+ include Collection::SharedLogic
28
+
26
29
  # Path to dfont file
27
30
  # @return [String]
28
31
  attr_reader :path
@@ -32,6 +35,22 @@ module Fontisan
32
35
  attr_reader :num_fonts
33
36
  alias font_count num_fonts
34
37
 
38
+ # Get font offsets (indices for dfont)
39
+ #
40
+ # dfont doesn't use byte offsets like TTC/OTC, so we return indices
41
+ #
42
+ # @return [Array<Integer>] Array of font indices
43
+ def font_offsets
44
+ (0...@num_fonts).to_a
45
+ end
46
+
47
+ # Get the collection format identifier
48
+ #
49
+ # @return [String] "dfont" for dfont collection
50
+ def self.collection_format
51
+ "dfont"
52
+ end
53
+
35
54
  # Load dfont collection from file
36
55
  #
37
56
  # @param path [String] Path to dfont file
@@ -65,6 +84,15 @@ module Fontisan
65
84
  File.exist?(@path) && @num_fonts.positive?
66
85
  end
67
86
 
87
+ # Get the collection version as a string
88
+ #
89
+ # dfont files don't have version numbers like TTC/OTC
90
+ #
91
+ # @return [String] Version string (always "N/A" for dfont)
92
+ def version_string
93
+ "N/A"
94
+ end
95
+
68
96
  # Extract all fonts from dfont
69
97
  #
70
98
  # @param io [IO] Open file handle
@@ -181,5 +209,61 @@ module Fontisan
181
209
 
182
210
  font
183
211
  end
212
+
213
+ # Get comprehensive collection metadata
214
+ #
215
+ # Returns a CollectionInfo model with header information and
216
+ # table sharing statistics for the dfont collection.
217
+ # This is the API method used by the `info` command for collections.
218
+ #
219
+ # @param io [IO] Open file handle to read fonts from
220
+ # @param path [String] Collection file path (for file size)
221
+ # @return [Models::CollectionInfo] Collection metadata
222
+ #
223
+ # @example Get collection info
224
+ # File.open("family.dfont", "rb") do |io|
225
+ # collection = DfontCollection.from_file("family.dfont")
226
+ # info = collection.collection_info(io, "family.dfont")
227
+ # puts "Format: #{info.collection_format}"
228
+ # end
229
+ def collection_info(io, path)
230
+ require_relative "models/collection_info"
231
+ require_relative "models/table_sharing_info"
232
+
233
+ # Calculate table sharing statistics
234
+ table_sharing = calculate_table_sharing(io)
235
+
236
+ # Get file size
237
+ file_size = path ? File.size(path) : 0
238
+
239
+ Models::CollectionInfo.new(
240
+ collection_path: path,
241
+ collection_format: self.class.collection_format,
242
+ ttc_tag: "dfnt", # dfont doesn't use ttcf tag
243
+ major_version: 0, # dfont doesn't have version
244
+ minor_version: 0,
245
+ num_fonts: @num_fonts,
246
+ font_offsets: font_offsets,
247
+ file_size_bytes: file_size,
248
+ table_sharing: table_sharing,
249
+ )
250
+ end
251
+
252
+ private
253
+
254
+ # Calculate table sharing statistics
255
+ #
256
+ # Analyzes which tables are shared between fonts and calculates
257
+ # space savings from deduplication.
258
+ #
259
+ # @param io [IO] Open file handle
260
+ # @return [Models::TableSharingInfo] Sharing statistics
261
+ def calculate_table_sharing(io)
262
+ # Extract all fonts
263
+ fonts = extract_fonts(io)
264
+
265
+ # Use shared logic for calculation
266
+ calculate_table_sharing_for_fonts(fonts)
267
+ end
184
268
  end
185
269
  end
@@ -99,11 +99,10 @@ module Fontisan
99
99
  # Check for TTC/OTC signature
100
100
  return true if signature == Constants::TTC_TAG
101
101
 
102
- # Check for multi-font dfont (suitcase) - only if it's actually a dfont
102
+ # Check for dfont - dfont is a collection format even if it contains only one font
103
103
  if signature == Constants::DFONT_RESOURCE_HEADER
104
104
  require_relative "parsers/dfont_parser"
105
- # Verify it's a valid dfont and has multiple fonts
106
- return Parsers::DfontParser.dfont?(io) && Parsers::DfontParser.sfnt_count(io) > 1
105
+ return Parsers::DfontParser.dfont?(io)
107
106
  end
108
107
 
109
108
  false
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fontisan
4
- VERSION = "0.2.6"
4
+ VERSION = "0.2.7"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fontisan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-05 00:00:00.000000000 Z
11
+ date: 2026-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -129,6 +129,7 @@ files:
129
129
  - lib/fontisan/collection/builder.rb
130
130
  - lib/fontisan/collection/dfont_builder.rb
131
131
  - lib/fontisan/collection/offset_calculator.rb
132
+ - lib/fontisan/collection/shared_logic.rb
132
133
  - lib/fontisan/collection/table_analyzer.rb
133
134
  - lib/fontisan/collection/table_deduplicator.rb
134
135
  - lib/fontisan/collection/writer.rb