Class: Fontisan::Woff2Font
- Inherits:
-
Object
- Object
- Fontisan::Woff2Font
- Defined in:
- lib/fontisan/woff2_font.rb
Overview
Web Open Font Format 2.0 (WOFF2) font domain object
Represents a WOFF2 font file that uses Brotli compression and table transformations. WOFF2 is significantly more complex than WOFF.
According to the WOFF2 specification (www.w3.org/TR/WOFF2/):
-
Tables can be transformed (glyf, loca, hmtx have special formats)
-
All compressed data in a single Brotli stream
-
Variable-length integer encoding (UIntBase128, 255UInt16)
-
More efficient compression than WOFF
Constant Summary collapse
- WOFF2_SIGNATURE =
WOFF2 signature constant
0x774F4632
Instance Attribute Summary collapse
-
#decompressed_tables ⇒ Object
Returns the value of attribute decompressed_tables.
-
#header ⇒ Object
Returns the value of attribute header.
-
#io_source ⇒ Object
Returns the value of attribute io_source.
-
#parsed_tables ⇒ Object
Returns the value of attribute parsed_tables.
-
#table_entries ⇒ Object
Returns the value of attribute table_entries.
Class Method Summary collapse
-
.from_file(path) ⇒ Woff2Font
Read WOFF2 font from a file.
Instance Method Summary collapse
-
#cff? ⇒ Boolean
Check if font is CFF flavored (OpenType with CFF outlines).
-
#find_table_entry(tag) ⇒ Woff2TableDirectoryEntry?
Find a table entry by tag.
-
#has_table?(tag) ⇒ Boolean
Check if font has a specific table.
-
#initialize ⇒ Woff2Font
constructor
A new instance of Woff2Font.
-
#initialize_storage ⇒ void
Initialize storage hashes.
-
#metadata ⇒ String?
Get WOFF2 metadata if present.
-
#read_from_io(io) ⇒ void
Read header and table directory from IO.
-
#table(tag) ⇒ Tables::*?
Get parsed table instance.
-
#table_data(tag) ⇒ String?
Get decompressed table data.
-
#table_names ⇒ Array<String>
Get list of all table tags.
-
#to_otf(output_path) ⇒ Integer
Convert WOFF2 to OTF format.
-
#to_ttf(output_path) ⇒ Integer
Convert WOFF2 to TTF format.
-
#truetype? ⇒ Boolean
Check if font is TrueType flavored.
-
#units_per_em ⇒ Integer?
Get units per em from head table.
-
#valid? ⇒ Boolean
Validate format correctness.
-
#validate_signature! ⇒ void
Validate WOFF2 signature.
Constructor Details
#initialize ⇒ Woff2Font
Returns a new instance of Woff2Font.
142 143 144 145 146 147 148 |
# File 'lib/fontisan/woff2_font.rb', line 142 def initialize @header = nil @table_entries = [] @decompressed_tables = {} @parsed_tables = {} @io_source = nil end |
Instance Attribute Details
#decompressed_tables ⇒ Object
Returns the value of attribute decompressed_tables.
110 111 112 |
# File 'lib/fontisan/woff2_font.rb', line 110 def decompressed_tables @decompressed_tables end |
#header ⇒ Object
Returns the value of attribute header.
110 111 112 |
# File 'lib/fontisan/woff2_font.rb', line 110 def header @header end |
#io_source ⇒ Object
Returns the value of attribute io_source.
110 111 112 |
# File 'lib/fontisan/woff2_font.rb', line 110 def io_source @io_source end |
#parsed_tables ⇒ Object
Returns the value of attribute parsed_tables.
110 111 112 |
# File 'lib/fontisan/woff2_font.rb', line 110 def parsed_tables @parsed_tables end |
#table_entries ⇒ Object
Returns the value of attribute table_entries.
110 111 112 |
# File 'lib/fontisan/woff2_font.rb', line 110 def table_entries @table_entries end |
Class Method Details
.from_file(path) ⇒ Woff2Font
Read WOFF2 font from a file
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/fontisan/woff2_font.rb', line 123 def self.from_file(path) if path.nil? || path.to_s.empty? raise ArgumentError, "path cannot be nil or empty" end raise Errno::ENOENT, "File not found: #{path}" unless File.exist?(path) File.open(path, "rb") do |io| font = new font.read_from_io(io) font.validate_signature! font.initialize_storage font.decompress_and_parse_tables(io) font.io_source = io font end rescue BinData::ValidityError, EOFError => e raise InvalidFontError, "Invalid WOFF2 file: #{e.}" end |
Instance Method Details
#cff? ⇒ Boolean
Check if font is CFF flavored (OpenType with CFF outlines)
190 191 192 |
# File 'lib/fontisan/woff2_font.rb', line 190 def cff? [Constants::SFNT_VERSION_OTTO, 0x4F54544F].include?(header.flavor) # 'OTTO' end |
#find_table_entry(tag) ⇒ Woff2TableDirectoryEntry?
Find a table entry by tag
216 217 218 |
# File 'lib/fontisan/woff2_font.rb', line 216 def find_table_entry(tag) table_entries.find { |entry| entry.tag == tag } end |
#has_table?(tag) ⇒ Boolean
Check if font has a specific table
208 209 210 |
# File 'lib/fontisan/woff2_font.rb', line 208 def has_table?(tag) table_entries.any? { |entry| entry.tag == tag } end |
#initialize_storage ⇒ void
This method returns an undefined value.
Initialize storage hashes
162 163 164 165 |
# File 'lib/fontisan/woff2_font.rb', line 162 def initialize_storage @decompressed_tables ||= {} @initialize_storage ||= {} end |
#metadata ⇒ String?
Get WOFF2 metadata if present
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/fontisan/woff2_font.rb', line 249 def return nil if header..zero? return @metadata if defined?(@metadata) File.open(io_source.path, "rb") do |io| io.seek(header.) = io.read(header.) @metadata = Brotli.inflate() # Verify decompressed size if @metadata.bytesize != header. raise InvalidFontError, "Metadata size mismatch: expected #{header.}, got #{@metadata.bytesize}" end @metadata end rescue StandardError => e warn "Failed to decompress WOFF2 metadata: #{e.}" @metadata = nil end |
#read_from_io(io) ⇒ void
This method returns an undefined value.
Read header and table directory from IO
154 155 156 157 |
# File 'lib/fontisan/woff2_font.rb', line 154 def read_from_io(io) @header = Woff2Header.read(io) read_table_directory(io) end |
#table(tag) ⇒ Tables::*?
Get parsed table instance
This method decompresses and parses the raw table data into a structured table object and caches the result for subsequent calls.
234 235 236 |
# File 'lib/fontisan/woff2_font.rb', line 234 def table(tag) @parsed_tables[tag] ||= parse_table(tag) end |
#table_data(tag) ⇒ String?
Get decompressed table data
Provides unified interface compatible with WoffFont
200 201 202 |
# File 'lib/fontisan/woff2_font.rb', line 200 def table_data(tag) @decompressed_tables[tag] end |
#table_names ⇒ Array<String>
Get list of all table tags
223 224 225 |
# File 'lib/fontisan/woff2_font.rb', line 223 def table_names table_entries.map(&:tag) end |
#to_otf(output_path) ⇒ Integer
Convert WOFF2 to OTF format
Decompresses and reconstructs tables, then builds a standard OTF file
294 295 296 297 298 299 300 301 |
# File 'lib/fontisan/woff2_font.rb', line 294 def to_otf(output_path) unless cff? raise InvalidFontError, "Cannot convert to OTF: font is TrueType flavored (use to_ttf)" end build_sfnt_font(output_path, Constants::SFNT_VERSION_OTTO) end |
#to_ttf(output_path) ⇒ Integer
Convert WOFF2 to TTF format
Decompresses and reconstructs tables, then builds a standard TTF file
278 279 280 281 282 283 284 285 |
# File 'lib/fontisan/woff2_font.rb', line 278 def to_ttf(output_path) unless truetype? raise InvalidFontError, "Cannot convert to TTF: font is CFF flavored (use to_otf)" end build_sfnt_font(output_path, Constants::SFNT_VERSION_TRUETYPE) end |
#truetype? ⇒ Boolean
Check if font is TrueType flavored
183 184 185 |
# File 'lib/fontisan/woff2_font.rb', line 183 def truetype? [Constants::SFNT_VERSION_TRUETYPE, 0x00010000].include?(header.flavor) end |
#units_per_em ⇒ Integer?
Get units per em from head table
241 242 243 244 |
# File 'lib/fontisan/woff2_font.rb', line 241 def units_per_em head = table(Constants::HEAD_TAG) head&.units_per_em end |
#valid? ⇒ Boolean
Validate format correctness
306 307 308 309 310 311 312 313 314 |
# File 'lib/fontisan/woff2_font.rb', line 306 def valid? return false unless header return false unless header.signature == WOFF2_SIGNATURE return false unless table_entries.respond_to?(:length) return false if table_entries.length != header.num_tables return false unless has_table?(Constants::HEAD_TAG) true end |
#validate_signature! ⇒ void
This method returns an undefined value.
Validate WOFF2 signature
171 172 173 174 175 176 177 178 |
# File 'lib/fontisan/woff2_font.rb', line 171 def validate_signature! signature_value = header.signature.to_i unless signature_value == WOFF2_SIGNATURE Kernel.raise(::Fontisan::InvalidFontError, "Invalid WOFF2 signature: expected 0x#{WOFF2_SIGNATURE.to_s(16)}, " \ "got 0x#{signature_value.to_s(16)}") end end |