fontisan 0.2.4 → 0.2.6

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +168 -32
  3. data/README.adoc +673 -1091
  4. data/lib/fontisan/cli.rb +94 -13
  5. data/lib/fontisan/collection/dfont_builder.rb +315 -0
  6. data/lib/fontisan/commands/convert_command.rb +118 -7
  7. data/lib/fontisan/commands/pack_command.rb +129 -22
  8. data/lib/fontisan/commands/validate_command.rb +107 -151
  9. data/lib/fontisan/config/conversion_matrix.yml +175 -1
  10. data/lib/fontisan/constants.rb +8 -0
  11. data/lib/fontisan/converters/collection_converter.rb +438 -0
  12. data/lib/fontisan/converters/woff2_encoder.rb +7 -29
  13. data/lib/fontisan/dfont_collection.rb +185 -0
  14. data/lib/fontisan/font_loader.rb +91 -6
  15. data/lib/fontisan/models/validation_report.rb +227 -0
  16. data/lib/fontisan/parsers/dfont_parser.rb +192 -0
  17. data/lib/fontisan/pipeline/transformation_pipeline.rb +4 -8
  18. data/lib/fontisan/tables/cmap.rb +82 -2
  19. data/lib/fontisan/tables/glyf.rb +118 -0
  20. data/lib/fontisan/tables/head.rb +60 -0
  21. data/lib/fontisan/tables/hhea.rb +74 -0
  22. data/lib/fontisan/tables/maxp.rb +60 -0
  23. data/lib/fontisan/tables/name.rb +76 -0
  24. data/lib/fontisan/tables/os2.rb +113 -0
  25. data/lib/fontisan/tables/post.rb +57 -0
  26. data/lib/fontisan/true_type_font.rb +8 -46
  27. data/lib/fontisan/validation/collection_validator.rb +265 -0
  28. data/lib/fontisan/validators/basic_validator.rb +85 -0
  29. data/lib/fontisan/validators/font_book_validator.rb +130 -0
  30. data/lib/fontisan/validators/opentype_validator.rb +112 -0
  31. data/lib/fontisan/validators/profile_loader.rb +139 -0
  32. data/lib/fontisan/validators/validator.rb +484 -0
  33. data/lib/fontisan/validators/web_font_validator.rb +102 -0
  34. data/lib/fontisan/version.rb +1 -1
  35. data/lib/fontisan.rb +78 -6
  36. metadata +13 -12
  37. data/lib/fontisan/config/validation_rules.yml +0 -149
  38. data/lib/fontisan/validation/checksum_validator.rb +0 -170
  39. data/lib/fontisan/validation/consistency_validator.rb +0 -197
  40. data/lib/fontisan/validation/structure_validator.rb +0 -198
  41. data/lib/fontisan/validation/table_validator.rb +0 -158
  42. data/lib/fontisan/validation/validator.rb +0 -152
  43. data/lib/fontisan/validation/variable_font_validator.rb +0 -218
  44. data/lib/fontisan/validation/woff2_header_validator.rb +0 -278
  45. data/lib/fontisan/validation/woff2_table_validator.rb +0 -270
  46. data/lib/fontisan/validation/woff2_validator.rb +0 -248
@@ -177,6 +177,91 @@ conversions:
177
177
  Same-format copy operation. Decompresses and re-compresses WOFF2 data
178
178
  with Brotli, useful for normalizing compression or updating metadata.
179
179
 
180
+ # Collection format conversions (TTC/OTC/dfont)
181
+ # Same-format collection operations
182
+ - from: ttc
183
+ to: ttc
184
+ strategy: collection_copier
185
+ description: "Copy TrueType Collection"
186
+ status: implemented
187
+ notes: >
188
+ Same-format copy operation for TTC files. Preserves all fonts and
189
+ table sharing structure.
190
+
191
+ - from: otc
192
+ to: otc
193
+ strategy: collection_copier
194
+ description: "Copy OpenType Collection"
195
+ status: implemented
196
+ notes: >
197
+ Same-format copy operation for OTC files. Preserves all fonts and
198
+ table sharing structure.
199
+
200
+ - from: dfont
201
+ to: dfont
202
+ strategy: collection_copier
203
+ description: "Copy Apple dfont suitcase"
204
+ status: implemented
205
+ notes: >
206
+ Same-format copy operation for dfont files. Preserves all fonts in
207
+ the suitcase.
208
+
209
+ # Cross-collection conversions
210
+ - from: ttc
211
+ to: otc
212
+ strategy: collection_converter
213
+ description: "Convert TrueType Collection to OpenType Collection"
214
+ status: implemented
215
+ notes: >
216
+ Unpacks TTC, converts all TrueType fonts to OpenType/CFF format,
217
+ then repacks as OTC with table sharing optimization.
218
+
219
+ - from: otc
220
+ to: ttc
221
+ strategy: collection_converter
222
+ description: "Convert OpenType Collection to TrueType Collection"
223
+ status: implemented
224
+ notes: >
225
+ Unpacks OTC, converts all OpenType/CFF fonts to TrueType format,
226
+ then repacks as TTC with table sharing optimization.
227
+
228
+ - from: ttc
229
+ to: dfont
230
+ strategy: collection_converter
231
+ description: "Convert TrueType Collection to dfont suitcase"
232
+ status: implemented
233
+ notes: >
234
+ Unpacks TTC and repacks as Apple dfont suitcase. No outline conversion
235
+ needed as dfont supports TrueType fonts.
236
+
237
+ - from: otc
238
+ to: dfont
239
+ strategy: collection_converter
240
+ description: "Convert OpenType Collection to dfont suitcase"
241
+ status: implemented
242
+ notes: >
243
+ Unpacks OTC and repacks as Apple dfont suitcase. No outline conversion
244
+ needed as dfont supports OpenType/CFF fonts.
245
+
246
+ - from: dfont
247
+ to: ttc
248
+ strategy: collection_converter
249
+ description: "Convert dfont suitcase to TrueType Collection"
250
+ status: implemented
251
+ notes: >
252
+ Unpacks dfont suitcase and repacks as TTC. If dfont contains OpenType
253
+ fonts, they are converted to TrueType format. Only valid if all fonts
254
+ can be converted to TrueType.
255
+
256
+ - from: dfont
257
+ to: otc
258
+ strategy: collection_converter
259
+ description: "Convert dfont suitcase to OpenType Collection"
260
+ status: implemented
261
+ notes: >
262
+ Unpacks dfont suitcase and repacks as OTC. If dfont contains TrueType
263
+ fonts, they are converted to OpenType/CFF format.
264
+
180
265
  # Conversion compatibility matrix
181
266
  #
182
267
  # This section documents which source features are preserved in conversions.
@@ -247,4 +332,93 @@ compatibility:
247
332
  use_cases:
248
333
  - fallback: Emergency fallback for very old browsers
249
334
  - conversion: Intermediate format for font conversion workflows
250
- - inspection: Easy-to-read format for font inspection
335
+ - inspection: Easy-to-read format for font inspection
336
+
337
+ collection_conversions:
338
+ ttc_to_otc:
339
+ preserves:
340
+ - all_fonts: All fonts in collection
341
+ - font_metrics: Font metrics for each font
342
+ - layout_features: Layout features for each font
343
+ limitations:
344
+ - outline_conversion_required: TrueType to CFF conversion applied
345
+ - hinting_loss: TrueType hinting not preserved in CFF
346
+ notes: >
347
+ Converts all TrueType fonts to OpenType/CFF format using the standard
348
+ TTF→OTF conversion pipeline, then repacks with table sharing.
349
+
350
+ otc_to_ttc:
351
+ preserves:
352
+ - all_fonts: All fonts in collection
353
+ - font_metrics: Font metrics for each font
354
+ - layout_features: Layout features for each font
355
+ limitations:
356
+ - outline_conversion_required: CFF to TrueType conversion applied
357
+ - hinting_loss: CFF hints not preserved in TrueType
358
+ - approximation: Cubic to quadratic curve approximation
359
+ notes: >
360
+ Converts all OpenType/CFF fonts to TrueType format using the standard
361
+ OTF→TTF conversion pipeline, then repacks with table sharing.
362
+
363
+ to_dfont:
364
+ preserves:
365
+ - all_fonts: All fonts in collection
366
+ - original_formats: Preserves original outline formats (TTF or OTF)
367
+ - font_metrics: All font metrics
368
+ - layout_features: All layout features
369
+ benefits:
370
+ - mixed_formats: dfont supports both TrueType and OpenType fonts
371
+ - mac_compatibility: Native Mac OS X suitcase format
372
+ limitations:
373
+ - platform_specific: Mac OS X only, not cross-platform
374
+ - no_table_sharing: dfont doesn't deduplicate tables like TTC/OTC
375
+ notes: >
376
+ Repackages fonts into Apple dfont suitcase format. No outline conversion
377
+ needed as dfont supports any SFNT font type (TTF, OTF, or mixed).
378
+
379
+ from_dfont:
380
+ preserves:
381
+ - all_fonts: All fonts in suitcase
382
+ - font_metrics: All font metrics
383
+ - layout_features: All layout features
384
+ limitations:
385
+ - conversion_may_be_required: Outline conversion if target requires specific format
386
+ - hinting_loss: If outline conversion occurs
387
+ benefits:
388
+ - cross_platform: TTC/OTC work on all platforms
389
+ - table_sharing: TTC/OTC optimize with table deduplication
390
+ notes: >
391
+ Extracts fonts from dfont suitcase and repacks into TTC or OTC.
392
+ Outline conversion may occur depending on target format requirements.
393
+
394
+ # Collection format rules
395
+ collection_rules:
396
+ ttc:
397
+ required_format: truetype
398
+ allows_mixed: false
399
+ spec_compliance: opentype_spec
400
+ description: >
401
+ TrueType Collection per OpenType specification. Contains only TrueType
402
+ fonts (glyf/loca tables). CFF fonts are not allowed.
403
+
404
+ otc:
405
+ required_format: cff_preferred
406
+ allows_mixed: true
407
+ spec_compliance: opentype_1.8
408
+ description: >
409
+ OpenType Collection per OpenType 1.8 specification. Requires at least
410
+ one CFF font. Fontisan extension allows mixed TTF+OTF for flexibility.
411
+
412
+ dfont:
413
+ required_format: any_sfnt
414
+ allows_mixed: true
415
+ spec_compliance: apple_proprietary
416
+ description: >
417
+ Apple dfont (Data Fork Font) suitcase. Contains Mac font suitcase
418
+ resources (FOND, NFNT, sfnt). Supports any SFNT fonts (TrueType,
419
+ OpenType/CFF, or mixed). Mac OS X specific, not cross-platform.
420
+
421
+ prohibited:
422
+ - web_fonts_in_collections: >
423
+ WOFF and WOFF2 fonts cannot be included in collections (TTC, OTC, or dfont).
424
+ Web fonts are designed for single-font web delivery only.
@@ -25,6 +25,14 @@ module Fontisan
25
25
  # SFNT version for OpenType fonts with CFF outlines ('OTTO')
26
26
  SFNT_VERSION_OTTO = 0x4F54544F
27
27
 
28
+ # Apple 'true' TrueType signature (alternate to 0x00010000)
29
+ SFNT_VERSION_TRUE = 0x74727965 # 'true' in ASCII
30
+
31
+ # dfont resource fork signatures
32
+ DFONT_RESOURCE_HEADER = "\x00\x00\x01\x00"
33
+ SFNT_RESOURCE_TYPE = "sfnt"
34
+ FOND_RESOURCE_TYPE = "FOND"
35
+
28
36
  # Head table tag identifier.
29
37
  # The 'head' table contains global font header information including
30
38
  # the checksum adjustment field.
@@ -0,0 +1,438 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "format_converter"
4
+ require_relative "../collection/builder"
5
+ require_relative "../collection/dfont_builder"
6
+ require_relative "../parsers/dfont_parser"
7
+ require_relative "../font_loader"
8
+
9
+ module Fontisan
10
+ module Converters
11
+ # CollectionConverter handles conversion between collection formats
12
+ #
13
+ # Main responsibility: Convert between TTC, OTC, and dfont collection
14
+ # formats using a three-step strategy:
15
+ # 1. Unpack: Extract individual fonts from source collection
16
+ # 2. Convert: Transform each font's outline format if requested
17
+ # 3. Repack: Rebuild collection in target format
18
+ #
19
+ # Supported conversions:
20
+ # - TTC ↔ OTC (preserves mixed TTF+OTF by default)
21
+ # - TTC → dfont (repackage)
22
+ # - OTC → dfont (repackage)
23
+ # - dfont → TTC (preserves mixed formats)
24
+ # - dfont → OTC (preserves mixed formats)
25
+ #
26
+ # @example Convert TTC to OTC (preserve formats)
27
+ # converter = CollectionConverter.new
28
+ # result = converter.convert(ttc_path, target_type: :otc, output: 'family.otc')
29
+ #
30
+ # @example Convert TTC to OTC with outline conversion
31
+ # converter = CollectionConverter.new
32
+ # result = converter.convert(ttc_path, target_type: :otc,
33
+ # options: { output: 'family.otc', convert_outlines: true })
34
+ #
35
+ # @example Convert dfont to TTC
36
+ # converter = CollectionConverter.new
37
+ # result = converter.convert(dfont_path, target_type: :ttc, output: 'family.ttc')
38
+ class CollectionConverter
39
+ # Convert collection to target format
40
+ #
41
+ # @param collection_path [String] Path to source collection
42
+ # @param target_type [Symbol] Target collection type (:ttc, :otc, :dfont)
43
+ # @param options [Hash] Conversion options
44
+ # @option options [String] :output Output file path (required)
45
+ # @option options [String] :target_format Target outline format: 'preserve' (default), 'ttf', or 'otf'
46
+ # @option options [Boolean] :optimize Enable table sharing (default: true, TTC/OTC only)
47
+ # @option options [Boolean] :verbose Enable verbose output (default: false)
48
+ # @return [Hash] Conversion result with:
49
+ # - :input [String] - Input collection path
50
+ # - :output [String] - Output collection path
51
+ # - :source_type [Symbol] - Source collection type
52
+ # - :target_type [Symbol] - Target collection type
53
+ # - :num_fonts [Integer] - Number of fonts converted
54
+ # - :conversions [Array<Hash>] - Per-font conversion details
55
+ # @raise [ArgumentError] if parameters invalid
56
+ # @raise [Error] if conversion fails
57
+ def convert(collection_path, target_type:, options: {})
58
+ validate_parameters!(collection_path, target_type, options)
59
+
60
+ verbose = options.fetch(:verbose, false)
61
+ output_path = options[:output]
62
+ target_format = options.fetch(:target_format, 'preserve').to_s
63
+
64
+ # Validate target_format
65
+ unless %w[preserve ttf otf].include?(target_format)
66
+ raise ArgumentError, "Invalid target_format: #{target_format}. Must be 'preserve', 'ttf', or 'otf'"
67
+ end
68
+
69
+ puts "Converting collection to #{target_type.to_s.upcase}..." if verbose
70
+
71
+ # Step 1: Unpack - extract fonts from source collection
72
+ puts " Unpacking fonts from source collection..." if verbose
73
+ fonts, source_type = unpack_fonts(collection_path)
74
+
75
+ # Check if conversion is needed
76
+ if source_type == target_type
77
+ puts " Source and target formats are the same, copying collection..." if verbose
78
+ FileUtils.cp(collection_path, output_path)
79
+ return build_result(collection_path, output_path, source_type, target_type, fonts.size, [])
80
+ end
81
+
82
+ # Step 2: Convert - transform fonts if requested
83
+ puts " Converting #{fonts.size} font(s)..." if verbose
84
+ converted_fonts, conversions = convert_fonts(fonts, source_type, target_type, options.merge(target_format: target_format))
85
+
86
+ # Step 3: Repack - build target collection
87
+ puts " Repacking into #{target_type.to_s.upcase} format..." if verbose
88
+ repack_fonts(converted_fonts, target_type, output_path, options)
89
+
90
+ # Build result
91
+ result = build_result(collection_path, output_path, source_type, target_type, fonts.size, conversions)
92
+
93
+ if verbose
94
+ display_result(result)
95
+ end
96
+
97
+ result
98
+ end
99
+
100
+ private
101
+
102
+ # Validate conversion parameters
103
+ #
104
+ # @param collection_path [String] Collection path
105
+ # @param target_type [Symbol] Target type
106
+ # @param options [Hash] Options
107
+ # @raise [ArgumentError] if invalid
108
+ def validate_parameters!(collection_path, target_type, options)
109
+ unless File.exist?(collection_path)
110
+ raise ArgumentError, "Collection file not found: #{collection_path}"
111
+ end
112
+
113
+ unless %i[ttc otc dfont].include?(target_type)
114
+ raise ArgumentError, "Invalid target type: #{target_type}. Must be :ttc, :otc, or :dfont"
115
+ end
116
+
117
+ unless options[:output]
118
+ raise ArgumentError, "Output path is required (:output option)"
119
+ end
120
+ end
121
+
122
+ # Unpack fonts from source collection
123
+ #
124
+ # @param collection_path [String] Collection path
125
+ # @return [Array<(Array<Font>, Symbol)>] Array of [fonts, source_type]
126
+ # @raise [Error] if unpacking fails
127
+ def unpack_fonts(collection_path)
128
+ # Detect collection type
129
+ source_type = detect_collection_type(collection_path)
130
+
131
+ fonts = case source_type
132
+ when :ttc, :otc
133
+ unpack_ttc_otc(collection_path)
134
+ when :dfont
135
+ unpack_dfont(collection_path)
136
+ else
137
+ raise Error, "Unknown collection type: #{source_type}"
138
+ end
139
+
140
+ [fonts, source_type]
141
+ end
142
+
143
+ # Detect collection type from file
144
+ #
145
+ # @param path [String] Collection path
146
+ # @return [Symbol] Collection type (:ttc, :otc, or :dfont)
147
+ def detect_collection_type(path)
148
+ File.open(path, "rb") do |io|
149
+ signature = io.read(4)
150
+ io.rewind
151
+
152
+ if signature == "ttcf"
153
+ # TTC or OTC - check extension
154
+ ext = File.extname(path).downcase
155
+ ext == ".otc" ? :otc : :ttc
156
+ elsif Parsers::DfontParser.dfont?(io)
157
+ :dfont
158
+ else
159
+ raise Error, "Not a valid collection file: #{path}"
160
+ end
161
+ end
162
+ end
163
+
164
+ # Unpack fonts from TTC/OTC
165
+ #
166
+ # @param path [String] TTC/OTC path
167
+ # @return [Array<Font>] Unpacked fonts
168
+ def unpack_ttc_otc(path)
169
+ collection = FontLoader.load_collection(path)
170
+
171
+ File.open(path, "rb") do |io|
172
+ collection.extract_fonts(io)
173
+ end
174
+ end
175
+
176
+ # Unpack fonts from dfont
177
+ #
178
+ # @param path [String] dfont path
179
+ # @return [Array<Font>] Unpacked fonts
180
+ def unpack_dfont(path)
181
+ fonts = []
182
+
183
+ File.open(path, "rb") do |io|
184
+ count = Parsers::DfontParser.sfnt_count(io)
185
+
186
+ count.times do |index|
187
+ sfnt_data = Parsers::DfontParser.extract_sfnt(io, index: index)
188
+
189
+ # Load font from SFNT binary
190
+ font = FontLoader.load_from_binary(sfnt_data)
191
+ fonts << font
192
+ end
193
+ end
194
+
195
+ fonts
196
+ end
197
+
198
+ # Convert fonts if outline format change needed
199
+ #
200
+ # @param fonts [Array<Font>] Source fonts
201
+ # @param source_type [Symbol] Source collection type
202
+ # @param target_type [Symbol] Target collection type
203
+ # @param options [Hash] Conversion options
204
+ # @return [Array<(Array<Font>, Array<Hash>)>] [converted_fonts, conversions]
205
+ def convert_fonts(fonts, source_type, target_type, options)
206
+ converted_fonts = []
207
+ conversions = []
208
+
209
+ # Determine if outline conversion is needed
210
+ target_format = options.fetch(:target_format, 'preserve').to_s
211
+
212
+ fonts.each_with_index do |font, index|
213
+ source_format = detect_font_format(font)
214
+ needs_conversion = outline_conversion_needed?(source_format, target_format)
215
+
216
+ if needs_conversion
217
+ # Convert outline format
218
+ desired_format = target_format == 'preserve' ? source_format : target_format.to_sym
219
+ converter = FormatConverter.new
220
+
221
+ begin
222
+ tables = converter.convert(font, desired_format, options)
223
+ converted_font = build_font_from_tables(tables, desired_format)
224
+ converted_fonts << converted_font
225
+
226
+ conversions << {
227
+ index: index,
228
+ source_format: source_format,
229
+ target_format: desired_format,
230
+ status: :converted,
231
+ }
232
+ rescue Error => e
233
+ # If conversion fails, keep original for dfont (supports mixed)
234
+ if target_type == :dfont
235
+ converted_fonts << font
236
+ conversions << {
237
+ index: index,
238
+ source_format: source_format,
239
+ target_format: source_format,
240
+ status: :preserved,
241
+ note: "Conversion failed, kept original: #{e.message}",
242
+ }
243
+ else
244
+ raise Error, "Font #{index} conversion failed: #{e.message}"
245
+ end
246
+ end
247
+ else
248
+ # No conversion needed, use original
249
+ converted_fonts << font
250
+ conversions << {
251
+ index: index,
252
+ source_format: source_format,
253
+ target_format: source_format,
254
+ status: :preserved,
255
+ }
256
+ end
257
+ end
258
+
259
+ [converted_fonts, conversions]
260
+ end
261
+
262
+ # Check if outline conversion is needed
263
+ #
264
+ # @param source_format [Symbol] Source font format (:ttf or :otf)
265
+ # @param target_format [String] Target format ('preserve', 'ttf', or 'otf')
266
+ # @return [Boolean] true if conversion needed
267
+ def outline_conversion_needed?(source_format, target_format)
268
+ # 'preserve' means keep original format
269
+ return false if target_format == 'preserve'
270
+
271
+ # Convert if target format differs from source
272
+ target_format.to_sym != source_format
273
+ end
274
+
275
+ # Determine target outline format for a font
276
+ #
277
+ # @param target_type [Symbol] Target collection type
278
+ # @param font [Font] Font object
279
+ # @return [Symbol] Target outline format (:ttf or :otf)
280
+ def target_outline_format(target_type, font)
281
+ case target_type
282
+ when :ttc
283
+ :ttf # TTC requires TrueType
284
+ when :otc
285
+ :otf # OTC requires OpenType/CFF
286
+ when :dfont
287
+ # dfont preserves original format
288
+ detect_font_format(font)
289
+ else
290
+ detect_font_format(font)
291
+ end
292
+ end
293
+
294
+ # Detect font outline format
295
+ #
296
+ # @param font [Font] Font object
297
+ # @return [Symbol] Format (:ttf or :otf)
298
+ def detect_font_format(font)
299
+ if font.has_table?("CFF ") || font.has_table?("CFF2")
300
+ :otf
301
+ elsif font.has_table?("glyf")
302
+ :ttf
303
+ else
304
+ raise Error, "Cannot detect font format"
305
+ end
306
+ end
307
+
308
+ # Build font object from tables
309
+ #
310
+ # @param tables [Hash] Table data
311
+ # @param format [Symbol] Font format
312
+ # @return [Font] Font object
313
+ def build_font_from_tables(tables, format)
314
+ # Create temporary font from tables
315
+ require_relative "../font_writer"
316
+ require "stringio"
317
+
318
+ sfnt_version = format == :otf ? 0x4F54544F : 0x00010000
319
+ binary = FontWriter.write_font(tables, sfnt_version: sfnt_version)
320
+
321
+ # Load font from binary using StringIO
322
+ sfnt_io = StringIO.new(binary)
323
+ signature = sfnt_io.read(4)
324
+ sfnt_io.rewind
325
+
326
+ # Create font based on signature
327
+ case signature
328
+ when [Constants::SFNT_VERSION_TRUETYPE].pack("N"), "true"
329
+ font = TrueTypeFont.read(sfnt_io)
330
+ font.initialize_storage
331
+ font.loading_mode = LoadingModes::FULL
332
+ font.lazy_load_enabled = false
333
+ font.read_table_data(sfnt_io)
334
+ font
335
+ when "OTTO"
336
+ font = OpenTypeFont.read(sfnt_io)
337
+ font.initialize_storage
338
+ font.loading_mode = LoadingModes::FULL
339
+ font.lazy_load_enabled = false
340
+ font.read_table_data(sfnt_io)
341
+ font
342
+ else
343
+ raise Error, "Invalid SFNT signature: #{signature.inspect}"
344
+ end
345
+ end
346
+
347
+ # Repack fonts into target collection
348
+ #
349
+ # @param fonts [Array<Font>] Fonts to pack
350
+ # @param target_type [Symbol] Target type
351
+ # @param output_path [String] Output path
352
+ # @param options [Hash] Packing options
353
+ # @return [void]
354
+ def repack_fonts(fonts, target_type, output_path, options)
355
+ case target_type
356
+ when :ttc, :otc
357
+ repack_ttc_otc(fonts, target_type, output_path, options)
358
+ when :dfont
359
+ repack_dfont(fonts, output_path, options)
360
+ else
361
+ raise Error, "Unknown target type: #{target_type}"
362
+ end
363
+ end
364
+
365
+ # Repack fonts into TTC/OTC
366
+ #
367
+ # @param fonts [Array<Font>] Fonts
368
+ # @param target_type [Symbol] :ttc or :otc
369
+ # @param output_path [String] Output path
370
+ # @param options [Hash] Options
371
+ # @return [void]
372
+ def repack_ttc_otc(fonts, target_type, output_path, options)
373
+ optimize = options.fetch(:optimize, true)
374
+
375
+ builder = Collection::Builder.new(
376
+ fonts,
377
+ format: target_type,
378
+ optimize: optimize,
379
+ )
380
+
381
+ builder.build_to_file(output_path)
382
+ end
383
+
384
+ # Repack fonts into dfont
385
+ #
386
+ # @param fonts [Array<Font>] Fonts
387
+ # @param output_path [String] Output path
388
+ # @param options [Hash] Options
389
+ # @return [void]
390
+ def repack_dfont(fonts, output_path, _options)
391
+ builder = Collection::DfontBuilder.new(fonts)
392
+ builder.build_to_file(output_path)
393
+ end
394
+
395
+ # Build conversion result
396
+ #
397
+ # @param input [String] Input path
398
+ # @param output [String] Output path
399
+ # @param source_type [Symbol] Source type
400
+ # @param target_type [Symbol] Target type
401
+ # @param num_fonts [Integer] Number of fonts
402
+ # @param conversions [Array<Hash>] Conversion details
403
+ # @return [Hash] Result
404
+ def build_result(input, output, source_type, target_type, num_fonts, conversions)
405
+ {
406
+ input: input,
407
+ output: output,
408
+ source_type: source_type,
409
+ target_type: target_type,
410
+ num_fonts: num_fonts,
411
+ conversions: conversions,
412
+ }
413
+ end
414
+
415
+ # Display conversion result
416
+ #
417
+ # @param result [Hash] Result
418
+ # @return [void]
419
+ def display_result(result)
420
+ puts "\n=== Collection Conversion Complete ==="
421
+ puts "Input: #{result[:input]}"
422
+ puts "Output: #{result[:output]}"
423
+ puts "Source format: #{result[:source_type].to_s.upcase}"
424
+ puts "Target format: #{result[:target_type].to_s.upcase}"
425
+ puts "Fonts: #{result[:num_fonts]}"
426
+
427
+ if result[:conversions].any?
428
+ converted_count = result[:conversions].count { |c| c[:status] == :converted }
429
+ if converted_count.positive?
430
+ puts "Outline conversions: #{converted_count}"
431
+ end
432
+ end
433
+
434
+ puts ""
435
+ end
436
+ end
437
+ end
438
+ end
@@ -6,7 +6,8 @@ require_relative "../woff2/directory"
6
6
  require_relative "../woff2/table_transformer"
7
7
  require_relative "../utilities/brotli_wrapper"
8
8
  require_relative "../utilities/checksum_calculator"
9
- require_relative "../validation/woff2_validator"
9
+ # Validation temporarily disabled - will be reimplemented with new DSL framework in Week 3+
10
+ # require_relative "../validation/woff2_validator"
10
11
  require "yaml"
11
12
  require "stringio"
12
13
 
@@ -111,10 +112,11 @@ module Fontisan
111
112
  result = { woff2_binary: woff2_binary }
112
113
 
113
114
  # Optional validation
114
- if options[:validate]
115
- validation_report = validate_encoding(woff2_binary, options)
116
- result[:validation_report] = validation_report
117
- end
115
+ # Temporarily disabled - will be reimplemented with new DSL framework
116
+ # if options[:validate]
117
+ # validation_report = validate_encoding(woff2_binary, options)
118
+ # result[:validation_report] = validation_report
119
+ # end
118
120
 
119
121
  result
120
122
  end
@@ -162,30 +164,6 @@ module Fontisan
162
164
 
163
165
  private
164
166
 
165
- # Validate encoded WOFF2 binary
166
- #
167
- # @param woff2_binary [String] Encoded WOFF2 data
168
- # @param options [Hash] Validation options
169
- # @return [Models::ValidationReport] Validation report
170
- def validate_encoding(woff2_binary, options)
171
- # Load the encoded WOFF2 from memory
172
- io = StringIO.new(woff2_binary)
173
- woff2_font = Woff2Font.from_file_io(io, "encoded.woff2")
174
-
175
- # Run validation
176
- validation_level = options[:validation_level] || :standard
177
- validator = Validation::Woff2Validator.new(level: validation_level)
178
- validator.validate(woff2_font, "encoded.woff2")
179
- rescue StandardError => e
180
- # If validation fails, create a report with the error
181
- report = Models::ValidationReport.new(
182
- font_path: "encoded.woff2",
183
- valid: false,
184
- )
185
- report.add_error("woff2_validation", "Validation failed: #{e.message}", nil)
186
- report
187
- end
188
-
189
167
  # Helper method to load WOFF2 from StringIO
190
168
  #
191
169
  # This is added to Woff2Font to support in-memory validation