Class: Fontist::Import::Google::FontDatabase

Inherits:
Object
  • Object
show all
Defined in:
lib/fontist/import/google/font_database.rb

Overview

Database for merged font data from API sources

Generates v4 formulas (TTF static only) or v5 formulas (all formats)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ttf_data:, vf_data: nil, woff2_data: nil, github_data: nil, version: 4, source_path: nil) ⇒ FontDatabase

Initialize database with API data

Parameters:

  • ttf_data (Array)

    TTF endpoint data

  • vf_data (Array, nil) (defaults to: nil)

    VF endpoint data (optional, for v5)

  • woff2_data (Array, nil) (defaults to: nil)

    WOFF2 endpoint data (optional, for v5)

  • github_data (Array, nil) (defaults to: nil)

    GitHub repository data (optional)

  • version (Integer) (defaults to: 4)

    Formula version (4 or 5)

  • source_path (String, nil) (defaults to: nil)

    Path to google/fonts repository (optional)



97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/fontist/import/google/font_database.rb', line 97

def initialize(ttf_data:, vf_data: nil, woff2_data: nil,
github_data: nil, version: 4, source_path: nil)
  @ttf_data = Array(ttf_data)
  @vf_data = Array(vf_data)
  @woff2_data = Array(woff2_data)
  @github_data_raw = Array(github_data)
  @version = version
  @source_path = source_path
  @ttf_files = {}
  @woff2_files = {}
  @github_index = index_github_data
  @github_data = @github_index # Expose indexed version
  @fonts = merge_data
end

Instance Attribute Details

#fontsObject (readonly)

Returns the value of attribute fonts.



20
21
22
# File 'lib/fontist/import/google/font_database.rb', line 20

def fonts
  @fonts
end

#github_dataObject (readonly)

Returns the value of attribute github_data.



20
21
22
# File 'lib/fontist/import/google/font_database.rb', line 20

def github_data
  @github_data
end

#ttf_filesObject (readonly)

Returns the value of attribute ttf_files.



20
21
22
# File 'lib/fontist/import/google/font_database.rb', line 20

def ttf_files
  @ttf_files
end

#versionObject (readonly)

Returns the value of attribute version.



20
21
22
# File 'lib/fontist/import/google/font_database.rb', line 20

def version
  @version
end

#woff2_filesObject (readonly)

Returns the value of attribute woff2_files.



20
21
22
# File 'lib/fontist/import/google/font_database.rb', line 20

def woff2_files
  @woff2_files
end

Class Method Details

.build(api_key:, source_path: nil) ⇒ FontDatabase

Generic build method for backward compatibility Delegates to build_v4 by default

Parameters:

  • api_key (String)

    Google Fonts API key

  • source_path (String, nil) (defaults to: nil)

    Path to google/fonts repository (optional)

Returns:



79
80
81
82
83
84
85
86
87
# File 'lib/fontist/import/google/font_database.rb', line 79

def self.build(api_key:, source_path: nil)
  if source_path
    build_v4(api_key: api_key, source_path: source_path)
  else
    # Build without GitHub data
    ttf_data = DataSources::Ttf.new(api_key: api_key).fetch
    new(ttf_data: ttf_data, version: 4, source_path: nil)
  end
end

.build_v4(api_key:, source_path:) ⇒ FontDatabase

Build database for v4 formulas (production)

V4 Requirements:

  • TTF format ONLY (no WOFF2)

  • Static fonts ONLY (exclude variable fonts)

  • OFL.txt license from GitHub repository

  • Complete metadata from Fontisan

Parameters:

  • api_key (String)

    Google Fonts API key

  • source_path (String)

    Path to google/fonts repository

Returns:



33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/fontist/import/google/font_database.rb', line 33

def self.build_v4(api_key:, source_path:)
  ttf_data = DataSources::Ttf.new(api_key: api_key).fetch
  # NO VF endpoint, NO WOFF2 endpoint for v4
  github_data = DataSources::Github.new(source_path: source_path).fetch

  new(
    ttf_data: ttf_data,
    github_data: github_data,
    version: 4,
    source_path: source_path,
  )
end

.build_v5(api_key:, source_path:) ⇒ FontDatabase

Build database for v5 formulas (future)

V5 will support:

  • TTF + WOFF2 formats

  • Static + Variable fonts

  • Enhanced ‘provides` attribute

  • Per-file resources

Parameters:

  • api_key (String)

    Google Fonts API key

  • source_path (String)

    Path to google/fonts repository

Returns:



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/fontist/import/google/font_database.rb', line 57

def self.build_v5(api_key:, source_path:)
  ttf_data = DataSources::Ttf.new(api_key: api_key).fetch
  vf_data = DataSources::Vf.new(api_key: api_key).fetch
  woff2_data = DataSources::Woff2.new(api_key: api_key).fetch
  github_data = DataSources::Github.new(source_path: source_path).fetch

  new(
    ttf_data: ttf_data,
    vf_data: vf_data,
    woff2_data: woff2_data,
    github_data: github_data,
    version: 5,
    source_path: source_path,
  )
end

Instance Method Details

#all_fontsObject

Get all font families



113
114
115
# File 'lib/fontist/import/google/font_database.rb', line 113

def all_fonts
  @fonts.values
end

#build_fonts_v4(family) ⇒ Object

Build fonts from API variant data with full metadata (v4: TTF only)



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/fontist/import/google/font_database.rb', line 283

def build_fonts_v4(family)
  parsed_fonts = []

  # V4: Download and parse ONLY TTF files to get complete metadata
  @ttf_files[family.family]&.each_value do |url|
    sleep(0.05) # Throttle API requests

    begin
      # Download font
      downloaded = Fontist::Utils::Downloader.download(url)

      # Parse with Fontisan
       = Fontist::Import::FontMetadataExtractor.new(downloaded.path).extract

      # V4: Skip variable fonts
      if .is_variable
        next
      end

      # Get filename from URL
      filename = url.split("/").last

      # Create style with complete metadata
      style_data = {
        family_name: .family_name,
        type: .subfamily_name,
        full_name: .full_name,
        post_script_name: .postscript_name,
        version: .version,
        copyright: .copyright,
        font: filename,
      }

      # Add preferred names if present
      if .preferred_family_name
        style_data[:preferred_family_name] =
          .preferred_family_name
      end
      if .preferred_subfamily_name
        style_data[:preferred_type] = .preferred_subfamily_name
      end

      # Add description if present
      if .description
        style_data[:description] = .description
      end

      parsed_fonts << style_data
    rescue StandardError => e
      warn "Warning: Failed to download/parse #{url}: #{e.message}"
    end
  end

  return [] if parsed_fonts.empty?

  # Group by subfamily (family_name from font, not API family)
  fonts_by_subfamily = parsed_fonts.group_by { |f| f[:family_name] }

  fonts_by_subfamily.map do |subfamily_name, styles|
    {
      "name" => subfamily_name,
      "styles" => styles.map { |s| stringify_style(s) },
    }
  end
end

#build_formula_v4(family) ⇒ Object

Build v4 formula from API data



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/fontist/import/google/font_database.rb', line 205

def build_formula_v4(family)
  github_family = @github_index[family.family]

  # Read license from GitHub if available
  license_url, license_text = if github_family&.license_text
                                [
                                  "https://scripts.sil.org/OFL",
                                  github_family.license_text,
                                ]
                              else
                                [
                                  "https://scripts.sil.org/OFL",
                                  "SIL Open Font License v1.1",
                                ]
                              end

  # Build fonts first to get copyright
  fonts_data = build_fonts_v4(family)

  # Extract copyright from first font style, or use license_text as fallback
  copyright = fonts_data.dig(0, "styles", 0,
                             "copyright") || github_family&.license_text

  # Use GitHub description if available
  description = github_family&.description || default_description(family)

  # Create import_source if available
  import_source = create_import_source(family)

  formula = {
    name: formula_name(family),
    description: description,
    homepage: default_homepage(family),
    resources: build_resources_v4(family),
    fonts: fonts_data,
    extract: {},
    copyright: copyright,
    license_url: license_url,
    license: license_text, # Changed from open_license
    open_license: license_text,
  }

  # Add import_source if available
  formula[:import_source] = import_source if import_source

  formula.compact
end

#build_resources_v4(family) ⇒ Object

Build resources from API URLs (v4: TTF only, no WOFF2)



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/fontist/import/google/font_database.rb', line 254

def build_resources_v4(family)
  files = []

  # V4: Collect ONLY TTF URLs from API (no WOFF2)
  @ttf_files[family.family]&.each_value do |url|
    files << url
  end

  return nil if files.empty?

  # V4: Always use "ttf" format (no variable fonts in v4)
  format = "ttf"

  resource = {
    "source" => "google",
    "family" => family.family,
    "files" => files,
    "format" => format,
  }

  # Add variable_axes if present
  if family.variable_font? && family.axes
    resource["variable_axes"] = family.axes.map(&:tag)
  end

  { family.family => resource }
end

#by_category(category) ⇒ Object

Filter fonts by category



123
124
125
# File 'lib/fontist/import/google/font_database.rb', line 123

def by_category(category)
  all_fonts.select { |font| font.category == category }
end

#categoriesObject

Get all unique categories



147
148
149
# File 'lib/fontist/import/google/font_database.rb', line 147

def categories
  all_fonts.map(&:category).compact.uniq.sort
end

#create_import_source(family) ⇒ GoogleImportSource?

Create GoogleImportSource for a font family

Parameters:

Returns:



441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/fontist/import/google/font_database.rb', line 441

def create_import_source(family)
  # Only create import_source if we have a commit_id
  commit = current_commit_id
  return nil unless commit

  Fontist::GoogleImportSource.new(
    commit_id: commit,
    api_version: "v1",
    last_modified: last_modified_for(family),
    family_id: family.family.downcase.tr(" ", "_"),
  )
end

#current_commit_idString?

Get current git commit from google/fonts repository

Returns:

  • (String, nil)

    Git commit SHA or nil if not available



418
419
420
421
422
423
424
425
426
# File 'lib/fontist/import/google/font_database.rb', line 418

def current_commit_id
  return @commit_id if defined?(@commit_id)

  @commit_id = if @source_path && File.directory?(@source_path)
                 get_git_commit(@source_path)
               end

  @commit_id
end

#default_description(family) ⇒ Object

Generate default description



386
387
388
# File 'lib/fontist/import/google/font_database.rb', line 386

def default_description(family)
  "#{family.family} font family"
end

#default_homepage(family) ⇒ Object

Generate default homepage



391
392
393
# File 'lib/fontist/import/google/font_database.rb', line 391

def default_homepage(family)
  "https://fonts.google.com/specimen/#{family.family.gsub(/\s+/, '+')}"
end

#find_font_filename_for_variant(family, variant) ⇒ Object

Find filename for a variant



357
358
359
360
361
362
363
364
365
366
367
# File 'lib/fontist/import/google/font_database.rb', line 357

def find_font_filename_for_variant(family, variant)
  # Try TTF first
  url = @ttf_files[family.family]&.[](variant)
  return url.split("/").last if url

  # Try WOFF2
  url = @woff2_files[family.family]&.[](variant)
  return url.split("/").last if url

  nil
end

#font_by_name(family_name) ⇒ Object

Find a specific font family by name



118
119
120
# File 'lib/fontist/import/google/font_database.rb', line 118

def font_by_name(family_name)
  @fonts[family_name]
end

#fonts_countObject

Get count of fonts by type



138
139
140
141
142
143
144
# File 'lib/fontist/import/google/font_database.rb', line 138

def fonts_count
  {
    total: all_fonts.count,
    variable: variable_fonts_only.count,
    static: static_fonts_only.count,
  }
end

#fonts_with_both_formatsObject

Get fonts available in both formats



162
163
164
165
166
# File 'lib/fontist/import/google/font_database.rb', line 162

def fonts_with_both_formats
  all_fonts.select do |font|
    @ttf_files.key?(font.family) && @woff2_files.key?(font.family)
  end
end

#fonts_with_ttfObject

Get fonts available in TTF format



152
153
154
# File 'lib/fontist/import/google/font_database.rb', line 152

def fonts_with_ttf
  all_fonts.select { |font| @ttf_files.key?(font.family) }
end

#fonts_with_woff2Object

Get fonts available in WOFF2 format



157
158
159
# File 'lib/fontist/import/google/font_database.rb', line 157

def fonts_with_woff2
  all_fonts.select { |font| @woff2_files.key?(font.family) }
end

#formula_name(family) ⇒ Object

Generate formula name from family name



381
382
383
# File 'lib/fontist/import/google/font_database.rb', line 381

def formula_name(family)
  family.family.downcase.gsub(/\s+/, "_")
end

#last_modified_for(family) ⇒ String

Get last modified timestamp for a font family

Parameters:

Returns:

  • (String)

    ISO 8601 timestamp



432
433
434
435
# File 'lib/fontist/import/google/font_database.rb', line 432

def last_modified_for(family)
  # Use last_modified from API metadata if available
  family.last_modified || Time.now.utc.iso8601
end

#save_formula(formula_hash, family_name, output_dir) ⇒ Object

Save formula to disk



396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/fontist/import/google/font_database.rb', line 396

def save_formula(formula_hash, family_name, output_dir)
  FileUtils.mkdir_p(output_dir)

  # Google Fonts formulas always use simple filenames
  # (Google Fonts is a live service, always pointing to latest version)
  base_name = family_name.downcase.gsub(/\s+/, "_")
  filename = "#{base_name}.yml"

  path = File.join(output_dir, filename)

  # Use HashHelper to convert keys to strings (preserves import_source object)
  require_relative "../helpers/hash_helper"
  formula_with_string_keys = Helpers::HashHelper.stringify_keys(formula_hash)

  File.write(path, YAML.dump(formula_with_string_keys))

  path
end

#save_formulas(output_dir, family_name: nil) ⇒ Object

Save formulas to disk



192
193
194
195
196
197
198
199
200
201
202
# File 'lib/fontist/import/google/font_database.rb', line 192

def save_formulas(output_dir, family_name: nil)
  families = family_name ? [font_by_name(family_name)] : all_fonts
  families = families.compact

  families.map do |family|
    formula = to_formula(family.family)
    next unless formula

    save_formula(formula, family.family, output_dir)
  end.compact
end

#static_fonts_onlyObject

Get only static fonts (fonts without axes)



133
134
135
# File 'lib/fontist/import/google/font_database.rb', line 133

def static_fonts_only
  all_fonts.reject(&:variable_font?)
end

#stringify_style(style) ⇒ Object

Convert style hash to string keys



350
351
352
353
354
# File 'lib/fontist/import/google/font_database.rb', line 350

def stringify_style(style)
  style.transform_keys(&:to_s).transform_values do |v|
    v.is_a?(Symbol) ? v.to_s : v
  end
end

#to_formula(family_name) ⇒ Object

Generate formula for a font family



179
180
181
182
183
184
# File 'lib/fontist/import/google/font_database.rb', line 179

def to_formula(family_name)
  family = font_by_name(family_name)
  return nil unless family

  build_formula_v4(family)
end

#to_formulasObject

Generate formulas for all fonts



187
188
189
# File 'lib/fontist/import/google/font_database.rb', line 187

def to_formulas
  all_fonts.map { |f| to_formula(f.family) }.compact
end

#ttf_files_for(family_name) ⇒ Object

Get TTF files for a specific font family



169
170
171
# File 'lib/fontist/import/google/font_database.rb', line 169

def ttf_files_for(family_name)
  @ttf_files[family_name]
end

#variable_fonts_onlyObject

Get only variable fonts (fonts with axes)



128
129
130
# File 'lib/fontist/import/google/font_database.rb', line 128

def variable_fonts_only
  all_fonts.select(&:variable_font?)
end

#variant_to_type(variant) ⇒ Object

Convert API variant to style type



370
371
372
373
374
375
376
377
378
# File 'lib/fontist/import/google/font_database.rb', line 370

def variant_to_type(variant)
  case variant
  when "regular" then "Regular"
  when "italic" then "Italic"
  when /^(\d+)italic$/ then "#{$1} Italic"
  when /^(\d+)$/ then variant
  else variant.capitalize
  end
end

#woff2_files_for(family_name) ⇒ Object

Get WOFF2 files for a specific font family



174
175
176
# File 'lib/fontist/import/google/font_database.rb', line 174

def woff2_files_for(family_name)
  @woff2_files[family_name]
end