Class: Fontisan::FontLoader
- Inherits:
-
Object
- Object
- Fontisan::FontLoader
- Defined in:
- lib/fontisan/font_loader.rb
Overview
FontLoader provides unified font loading with automatic format detection.
This class is the primary entry point for loading fonts in Fontisan. It automatically detects the font format and returns the appropriate domain object (TrueTypeFont, OpenTypeFont, TrueTypeCollection, or OpenTypeCollection).
Class Method Summary collapse
-
.collection?(path) ⇒ Boolean
Check if a file is a collection (TTC or OTC).
-
.load(path, font_index: 0, mode: nil, lazy: nil) ⇒ TrueTypeFont, ...
Load a font from file with automatic format detection.
-
.load_collection(path) ⇒ TrueTypeCollection, OpenTypeCollection
Load a collection object without extracting fonts.
Class Method Details
.collection?(path) ⇒ Boolean
Check if a file is a collection (TTC or OTC)
90 91 92 93 94 95 96 97 |
# File 'lib/fontisan/font_loader.rb', line 90 def self.collection?(path) raise Errno::ENOENT, "File not found: #{path}" unless File.exist?(path) File.open(path, "rb") do |io| signature = io.read(4) signature == Constants::TTC_TAG end end |
.load(path, font_index: 0, mode: nil, lazy: nil) ⇒ TrueTypeFont, ...
Load a font from file with automatic format detection
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/fontisan/font_loader.rb', line 44 def self.load(path, font_index: 0, mode: nil, lazy: nil) raise Errno::ENOENT, "File not found: #{path}" unless File.exist?(path) # Resolve mode and lazy parameters with environment variables resolved_mode = mode || env_mode || LoadingModes::FULL resolved_lazy = if lazy.nil? env_lazy.nil? ? false : env_lazy else lazy end # Validate mode LoadingModes.validate_mode!(resolved_mode) File.open(path, "rb") do |io| signature = io.read(4) io.rewind case signature when Constants::TTC_TAG load_from_collection(io, path, font_index, mode: resolved_mode, lazy: resolved_lazy) when pack_uint32(Constants::SFNT_VERSION_TRUETYPE) TrueTypeFont.from_file(path, mode: resolved_mode, lazy: resolved_lazy) when "OTTO" OpenTypeFont.from_file(path, mode: resolved_mode, lazy: resolved_lazy) when "wOFF" WoffFont.from_file(path, mode: resolved_mode, lazy: resolved_lazy) when "wOF2" Woff2Font.from_file(path, mode: resolved_mode, lazy: resolved_lazy) else raise InvalidFontError, "Unknown font format. Expected TTF, OTF, TTC, OTC, WOFF, or WOFF2 file." end end end |
.load_collection(path) ⇒ TrueTypeCollection, OpenTypeCollection
Load a collection object without extracting fonts
Returns the collection object (TrueTypeCollection or OpenTypeCollection) without extracting individual fonts. Useful for inspecting collection metadata and structure.
Collection Format Understanding
Both TTC (TrueType Collection) and OTC (OpenType Collection) files use the same “ttcf” signature. The distinction between TTC and OTC is NOT in the collection format itself, but in the fonts contained within:
-
TTC typically contains TrueType fonts (glyf outlines)
-
OTC typically contains OpenType fonts (CFF/CFF2 outlines)
-
Mixed collections are possible (both TTF and OTF in same collection)
Each collection can contain multiple SFNT-format font files, with table deduplication to save space. Individual fonts within a collection are stored at different offsets within the file, each with their own table directory and data tables.
Detection Strategy
This method scans ALL fonts in the collection to determine the collection type accurately:
-
Reads all font offsets from the collection header
-
Examines the sfnt_version of each font in the collection
-
Counts TrueType fonts (0x00010000 or 0x74727565 “true”) vs OpenType fonts (0x4F54544F “OTTO”)
-
If ANY font is OpenType (CFF), returns OpenTypeCollection
-
Only returns TrueTypeCollection if ALL fonts are TrueType
This approach correctly handles:
-
Homogeneous collections (all TTF or all OTF)
-
Mixed collections (both TTF and OTF fonts) - uses OpenTypeCollection
-
Large collections with many fonts (like NotoSerifCJK.ttc with 35 fonts)
144 145 146 147 148 149 150 151 152 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 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/fontisan/font_loader.rb', line 144 def self.load_collection(path) raise Errno::ENOENT, "File not found: #{path}" unless File.exist?(path) File.open(path, "rb") do |io| signature = io.read(4) unless signature == Constants::TTC_TAG raise InvalidFontError, "File is not a collection (TTC/OTC). Use FontLoader.load instead." end # Read version and num_fonts io.seek(8) # Skip tag (4) + version (4) num_fonts = io.read(4).unpack1("N") # Read all font offsets font_offsets = num_fonts.times.map { io.read(4).unpack1("N") } # Scan all fonts to determine collection type (not just first) truetype_count = 0 opentype_count = 0 font_offsets.each do |offset| io.rewind io.seek(offset) sfnt_version = io.read(4).unpack1("N") case sfnt_version when Constants::SFNT_VERSION_TRUETYPE, 0x74727565 # 0x74727565 = 'true' truetype_count += 1 when Constants::SFNT_VERSION_OTTO opentype_count += 1 else raise InvalidFontError, "Unknown font type in collection at offset #{offset} (sfnt version: 0x#{sfnt_version.to_s(16)})" end end io.rewind # Determine collection type based on what fonts are inside # If ANY font is OpenType, use OpenTypeCollection (more general format) # Only use TrueTypeCollection if ALL fonts are TrueType if opentype_count > 0 OpenTypeCollection.from_file(path) else # All fonts are TrueType TrueTypeCollection.from_file(path) end end end |