Class: Fontisan::Collection::Builder

Inherits:
Object
  • Object
show all
Defined in:
lib/fontisan/collection/builder.rb

Overview

CollectionBuilder orchestrates TTC/OTC creation

Main responsibility: Coordinate the entire collection creation process including analysis, deduplication, offset calculation, and writing. Implements builder pattern for flexible configuration.

Examples:

Create TTC with default options

builder = CollectionBuilder.new([font1, font2, font3])
builder.build_to_file("family.ttc")

Create OTC with optimization

builder = CollectionBuilder.new([font1, font2, font3])
builder.format = :otc
builder.optimize = true
result = builder.build
puts "Saved #{result[:space_savings]} bytes"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(fonts, options = {}) ⇒ Builder

Initialize builder with fonts

Parameters:

Options Hash (options):

  • :format (Symbol)

    Format type (:ttc or :otc, default: :ttc)

  • :optimize (Boolean)

    Enable optimization (default: true)

  • :config (Hash)

    Configuration overrides

Raises:

  • (ArgumentError)

    if fonts array is invalid



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/fontisan/collection/builder.rb', line 52

def initialize(fonts, options = {})
  if fonts.nil? || fonts.empty?
    raise ArgumentError,
          "fonts cannot be nil or empty"
  end
  raise ArgumentError, "fonts must be an array" unless fonts.is_a?(Array)

  unless fonts.all? do |f|
    f.respond_to?(:table_data)
  end
    raise ArgumentError,
          "all fonts must respond to table_data"
  end

  @fonts = fonts
  @format = options[:format] || :ttc
  @optimize = options.fetch(:optimize, true)
  @config = load_config.merge(options[:config] || {})
  @result = nil

  validate_format!
end

Instance Attribute Details

#configHash

Configuration settings

Returns:

  • (Hash)


38
39
40
# File 'lib/fontisan/collection/builder.rb', line 38

def config
  @config
end

#fontsArray<TrueTypeFont, OpenTypeFont> (readonly)

Source fonts

Returns:



26
27
28
# File 'lib/fontisan/collection/builder.rb', line 26

def fonts
  @fonts
end

#formatSymbol

Collection format (:ttc or :otc)

Returns:

  • (Symbol)


30
31
32
# File 'lib/fontisan/collection/builder.rb', line 30

def format
  @format
end

#optimizeBoolean

Enable table sharing optimization

Returns:

  • (Boolean)


34
35
36
# File 'lib/fontisan/collection/builder.rb', line 34

def optimize
  @optimize
end

#resultHash? (readonly)

Build result (populated after build)

Returns:

  • (Hash, nil)


42
43
44
# File 'lib/fontisan/collection/builder.rb', line 42

def result
  @result
end

Instance Method Details

#analyzeHash

Get analysis report

Runs analysis without building the full collection. Useful for previewing space savings before committing to build.

Returns:

  • (Hash)

    Analysis report



137
138
139
140
# File 'lib/fontisan/collection/builder.rb', line 137

def analyze
  analyzer = TableAnalyzer.new(@fonts)
  analyzer.analyze
end

#buildHash

Build collection and return binary

Executes the complete collection creation process:

  1. Analyze tables across fonts
  2. Deduplicate identical tables
  3. Calculate file offsets
  4. Write binary structure

Returns:

  • (Hash)

    Build result with:

    • :binary [String] - Complete collection binary
    • :space_savings [Integer] - Bytes saved by sharing
    • :analysis [Hash] - Analysis report
    • :statistics [Hash] - Deduplication statistics


88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/fontisan/collection/builder.rb', line 88

def build
  # Step 1: Analyze tables
  analyzer = TableAnalyzer.new(@fonts)
  analysis_report = analyzer.analyze

  # Step 2: Deduplicate tables
  deduplicator = TableDeduplicator.new(@fonts)
  sharing_map = deduplicator.build_sharing_map
  statistics = deduplicator.statistics

  # Step 3: Calculate offsets
  calculator = OffsetCalculator.new(sharing_map, @fonts)
  offsets = calculator.calculate

  # Step 4: Write collection
  writer = Writer.new(@fonts, sharing_map, offsets, format: @format)
  binary = writer.write_collection

  # Store result
  @result = {
    binary: binary,
    space_savings: analysis_report[:space_savings],
    analysis: analysis_report,
    statistics: statistics,
    format: @format,
    num_fonts: @fonts.size,
  }

  @result
end

#build_to_file(path) ⇒ Hash

Build collection and write to file

Parameters:

  • path (String)

    Output file path

Returns:

  • (Hash)

    Build result (same as build method)



123
124
125
126
127
128
129
# File 'lib/fontisan/collection/builder.rb', line 123

def build_to_file(path)
  result = build
  File.binwrite(path, result[:binary])
  result[:output_path] = path
  result[:output_size] = result[:binary].bytesize
  result
end

#potential_savingsInteger

Get potential space savings without building

Returns:

  • (Integer)

    Bytes that can be saved



145
146
147
# File 'lib/fontisan/collection/builder.rb', line 145

def potential_savings
  analyze[:space_savings]
end

#validate!Boolean

Validate collection can be built

Returns:

  • (Boolean)

    true if valid, raises error otherwise

Raises:

  • (Error)

    if validation fails



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/fontisan/collection/builder.rb', line 153

def validate!
  # Check minimum fonts
  raise Error, "Collection requires at least 2 fonts" if @fonts.size < 2

  # Check format compatibility
  incompatible = check_format_compatibility
  if incompatible.any?
    raise Error, "Format mismatch: #{incompatible.join(', ')}"
  end

  # Check variable font compatibility
  validate_variation_compatibility! if variable_fonts_in_collection?

  # Check all fonts have required tables
  @fonts.each_with_index do |font, index|
    required_tables = %w[head hhea maxp]
    missing = required_tables.reject { |tag| font.has_table?(tag) }
    unless missing.empty?
      raise Error,
            "Font #{index} missing required tables: #{missing.join(', ')}"
    end
  end

  true
end

#validate_variation_compatibility!void

This method returns an undefined value.

Validate variable font compatibility

Ensures all variable fonts in the collection are compatible:

  • All must be same variation type (TrueType or CFF2)
  • All must have the same axes

Raises:

  • (Error)

    if variable fonts are incompatible



194
195
196
197
# File 'lib/fontisan/collection/builder.rb', line 194

def validate_variation_compatibility!
  validate_all_same_variation_type!
  validate_same_axes!
end

#variable_fonts_in_collection?Boolean

Check if collection contains variable fonts

Returns:

  • (Boolean)

    true if any font has fvar table



182
183
184
# File 'lib/fontisan/collection/builder.rb', line 182

def variable_fonts_in_collection?
  @fonts.any? { |font| font.has_table?("fvar") }
end