Class: Fontisan::FontLoader

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

Overview

FontLoader provides unified font loading with content-based format detection.

This class is the primary entry point for loading fonts in Fontisan. It inspects each file's magic bytes to determine the on-disk format and returns the appropriate domain object (TrueTypeFont, OpenTypeFont, Type1Font, TrueTypeCollection, or OpenTypeCollection).

Detection is purely content-based — the file extension is ignored. This matters because vendors occasionally ship files with a misleading extension (e.g. Apple ships a single OpenType-CFF font as .ttc in macOS's private FontServices framework).

Examples:

Load any font type

font = FontLoader.load("font.ttf")  # => TrueTypeFont
font = FontLoader.load("font.otf")  # => OpenTypeFont
font = FontLoader.load("font.pfb")  # => Type1Font
font = FontLoader.load("font.pfa")  # => Type1Font
font = FontLoader.load("fonts.ttc") # => TrueTypeFont (first in collection)
font = FontLoader.load("fonts.ttc", font_index: 2) # => TrueTypeFont (third in collection)

Loading modes

font = FontLoader.load("font.ttf", mode: :metadata)  # Load only metadata tables
font = FontLoader.load("font.ttf", mode: :full)      # Load all tables

Lazy loading control

font = FontLoader.load("font.ttf", lazy: true)   # Tables loaded on-demand
font = FontLoader.load("font.ttf", lazy: false)  # All tables loaded upfront

Class Method Summary collapse

Class Method Details

.collection?(path) ⇒ Boolean

Check if a file is a collection (TTC, OTC, or dfont).

Returns false for a ttcf-headed file whose inner fonts can't be classified (truncated header, offsets past EOF, unrecognised inner SFNT versions). Such a file is structurally invalid as a collection and would fail to load, so reporting it as "not a collection" matches what callers can actually do with it.

Examples:

Check if file is collection

FontLoader.collection?("fonts.ttc") # => true
FontLoader.collection?("font.ttf")  # => false

Parameters:

  • path (String)

    Path to the font file

Returns:

  • (Boolean)

    true if file is a loadable collection

Raises:

  • (Errno::ENOENT)

    if file does not exist



110
# File 'lib/fontisan/font_loader.rb', line 110

def self.collection?(path) = COLLECTION_CLASSES.key?(detect(path))

.detect_format(path) ⇒ Symbol?

Identify a font file by inspecting its magic bytes (content-based detection).

Returns the actual on-disk format regardless of the file extension. This is the authoritative way to determine how a file should be parsed, because vendors occasionally ship files with a misleading extension (for example, Apple ships a single OpenType-CFF font as .ttc in macOS's private FontServices framework).

Collections are distinguished by scanning the inner fonts: if any inner font is OpenType (CFF), the file is reported as :otc; otherwise (all inner fonts are TrueType) it is reported as :ttc. A ttcf-headed file whose inner fonts can't be classified (truncated header, offsets past EOF, unrecognised inner SFNT versions) returns nil. dfont detection uses the canonical resource-data-offset (256) magic only; non-canonical but structurally valid dfonts are accepted by load_collection as a fallback but not reported here.

Examples:

Detect a real collection

FontLoader.detect_format("fonts.ttc")          # => :ttc

Detect a single OTF mislabeled as .ttc

FontLoader.detect_format("SauberScript.ttc")   # => :otf

Parameters:

  • path (String)

    Path to the font file

Returns:

  • (Symbol, nil)

    One of :ttf, :otf, :ttc, :otc, :woff, :woff2, :dfont, :pfa, :pfb, or nil when the format is not recognised.

Raises:

  • (Errno::ENOENT)

    if the file does not exist



140
# File 'lib/fontisan/font_loader.rb', line 140

def self.detect_format(path) = detect(path)

.load(path, font_index: 0, mode: nil, lazy: nil) ⇒ TrueTypeFont, ...

Load a font from file with content-based format detection.

The file's bytes determine its format; the extension is ignored. See detect_format for the full list of recognised formats and how they are detected.

Parameters:

  • path (String)

    Path to the font file

  • font_index (Integer) (defaults to: 0)

    Index of font in collection (0-based, default: 0)

  • mode (Symbol) (defaults to: nil)

    Loading mode (:metadata or :full, default: from ENV or :full)

  • lazy (Boolean) (defaults to: nil)

    If true, load tables on demand (default: false for eager loading)

Returns:

Raises:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/fontisan/font_loader.rb', line 65

def self.load(path, font_index: 0, mode: nil, lazy: nil)
  resolved_mode = mode || env_mode || LoadingModes::FULL
  resolved_lazy = if lazy.nil?
                    env_lazy.nil? ? false : env_lazy
                  else
                    lazy
                  end
  LoadingModes.validate_mode!(resolved_mode)

  format = detect(path)
  case format
  when :ttf   then TrueTypeFont.from_file(path, mode: resolved_mode,
                                                lazy: resolved_lazy)
  when :otf   then OpenTypeFont.from_file(path, mode: resolved_mode,
                                                lazy: resolved_lazy)
  when :woff  then WoffFont.from_file(path, mode: resolved_mode,
                                            lazy: resolved_lazy)
  when :woff2 then Woff2Font.from_file(path, mode: resolved_mode,
                                             lazy: resolved_lazy)
  when :ttc, :otc then load_from_collection(path, format, font_index,
                                            mode: resolved_mode)
  when :dfont then load_dfont(path, font_index: font_index,
                                    mode: resolved_mode)
  when :pfa, :pfb then Type1Font.from_file(path, mode: resolved_mode)
  else
    raise InvalidFontError,
          "Unknown font format. Expected TTF, OTF, TTC, OTC, WOFF, WOFF2, PFB, or PFA file."
  end
end

.load_collection(path) ⇒ TrueTypeCollection, ...

Load a collection object without extracting fonts

Returns the collection object (TrueTypeCollection, OpenTypeCollection, or DfontCollection) without extracting individual fonts. Useful for inspecting collection metadata and structure.

The TTC vs. OTC distinction is resolved by detect_format, which scans the inner fonts; see that method for details.

Examples:

Load collection for inspection

collection = FontLoader.load_collection("fonts.ttc")
puts "Collection has #{collection.num_fonts} fonts"

Parameters:

  • path (String)

    Path to the collection file

Returns:

Raises:

  • (Errno::ENOENT)

    if file does not exist

  • (InvalidFontError)

    if file is not a collection or type cannot be determined



159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/fontisan/font_loader.rb', line 159

def self.load_collection(path)
  format = detect(path)
  return COLLECTION_CLASSES.fetch(format).from_file(path) if COLLECTION_CLASSES.key?(format)

  # Lenient fallback: a dfont whose resource-data offset isn't the
  # canonical 256 fails the strict magic test in {.detect} but may still
  # be structurally valid; try the structural check before giving up.
  File.open(path, "rb") do |io|
    return DfontCollection.from_file(path) if Parsers::DfontParser.dfont?(io)
  end
  raise InvalidFontError,
        "File is not a collection (TTC/OTC/dfont). Use FontLoader.load instead."
end