Class: Fontisan::GlyphAccessor
- Inherits:
-
Object
- Object
- Fontisan::GlyphAccessor
- Defined in:
- lib/fontisan/glyph_accessor.rb
Overview
High-level utility class for unified glyph access across font formats
GlyphAccessor provides a clean, unified
interface for accessing glyphs regardless of the underlying font format
(TrueType with glyf table or OpenType with CFF table).
This class automatically detects the font format and delegates to the appropriate table parser, abstracting away the complexity of different glyph storage mechanisms.
Key features:
- Unified glyph access by ID, Unicode character, or PostScript name
- Automatic format detection (TrueType glyf vs CFF)
- Metrics retrieval (advance width, left sidebearing)
- Glyph closure calculation for subsetting (tracks composite dependencies)
- Validation of glyph IDs and character mappings
- Bounded LRU cache to prevent unbounded memory growth
Reference: docs/ttfunk-feature-analysis.md:541-575
Constant Summary collapse
- MAX_GLYPH_CACHE_SIZE =
Maximum number of glyphs to cache before LRU eviction
10_000
Instance Attribute Summary collapse
-
#font ⇒ TrueTypeFont, OpenTypeFont
readonly
Font instance this accessor operates on.
Instance Method Summary collapse
-
#cff? ⇒ Boolean
Check if font uses CFF outlines (CFF table).
-
#clear_cache ⇒ void
Clear internal caches to free memory.
-
#closure_for(glyph_ids) ⇒ Set<Integer>
Calculate glyph closure for subsetting.
-
#glyph_exists?(glyph_id) ⇒ Boolean
Check if a glyph ID exists and is valid.
-
#glyph_for_char(char_code) ⇒ SimpleGlyph, ...
Get glyph object for a Unicode character code.
-
#glyph_for_id(glyph_id) ⇒ SimpleGlyph, ...
Get glyph object for a glyph ID.
-
#glyph_for_name(glyph_name) ⇒ SimpleGlyph, ...
Get glyph object for a PostScript glyph name.
-
#has_glyph_for_char?(char_code) ⇒ Boolean
Check if a Unicode character is mapped in the font.
-
#initialize(font) ⇒ GlyphAccessor
constructor
Initialize a new glyph accessor.
-
#metrics_for_char(char_code) ⇒ Hash{Symbol => Integer}?
Get horizontal metrics for a Unicode character.
-
#metrics_for_id(glyph_id) ⇒ Hash{Symbol => Integer}?
Get horizontal metrics for a glyph ID.
-
#outline_for_char(char) ⇒ Models::GlyphOutline?
Get outline for character.
-
#outline_for_codepoint(codepoint) ⇒ Models::GlyphOutline?
Get outline for Unicode codepoint.
-
#outline_for_id(glyph_id) ⇒ Models::GlyphOutline?
Get outline for glyph by ID.
-
#truetype? ⇒ Boolean
Check if font uses TrueType outlines (glyf table).
Constructor Details
#initialize(font) ⇒ GlyphAccessor
Initialize a new glyph accessor
55 56 57 58 59 60 61 62 |
# File 'lib/fontisan/glyph_accessor.rb', line 55 def initialize(font) raise ArgumentError, "Font cannot be nil" if font.nil? @font = font @glyph_cache = {} @closure_cache = {} @glyph_access_times = {} end |
Instance Attribute Details
#font ⇒ TrueTypeFont, OpenTypeFont (readonly)
Font instance this accessor operates on
49 50 51 |
# File 'lib/fontisan/glyph_accessor.rb', line 49 def font @font end |
Instance Method Details
#cff? ⇒ Boolean
Check if font uses CFF outlines (CFF table)
298 299 300 |
# File 'lib/fontisan/glyph_accessor.rb', line 298 def cff? font.table("CFF ") != nil end |
#clear_cache ⇒ void
This method returns an undefined value.
Clear internal caches to free memory
Useful for long-running processes that access many glyphs.
374 375 376 377 378 379 380 381 382 |
# File 'lib/fontisan/glyph_accessor.rb', line 374 def clear_cache @glyph_cache.clear @closure_cache.clear @glyph_access_times.clear # Also clear glyf table cache if present glyf = font.table("glyf") glyf&.clear_cache end |
#closure_for(glyph_ids) ⇒ Set<Integer>
Calculate glyph closure for subsetting
This method recursively tracks all glyphs needed for a given set of glyph IDs, including component glyphs referenced by compound glyphs. This is essential for font subsetting to ensure all required glyphs are included.
The closure always includes glyph 0 (.notdef) as required by the OpenType specification.
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 |
# File 'lib/fontisan/glyph_accessor.rb', line 328 def closure_for(glyph_ids) unless glyph_ids.is_a?(Array) raise ArgumentError, "glyph_ids must be an Array" end # Start with provided glyphs plus .notdef result = Set.new([0]) glyph_ids.each { |id| result.add(id) if glyph_exists?(id) } # CFF fonts have no composite glyphs, so return early return result if cff? # Recursively collect composite dependencies (TrueType only) to_process = result.to_a.dup processed = Set.new while (glyph_id = to_process.shift) next if processed.include?(glyph_id) processed.add(glyph_id) # Get glyph and check if it's compound glyph = glyph_for_id(glyph_id) next unless glyph next unless glyph&.compound? && glyph.compound? # Add component glyph IDs glyph&.components&.each do |component| component_id = component[:glyph_index] next unless glyph_exists?(component_id) unless result.include?(component_id) result.add(component_id) to_process << component_id end end end result end |
#glyph_exists?(glyph_id) ⇒ Boolean
Check if a glyph ID exists and is valid
271 272 273 274 275 276 277 278 |
# File 'lib/fontisan/glyph_accessor.rb', line 271 def glyph_exists?(glyph_id) return false if glyph_id.nil? || glyph_id.negative? maxp = font.table("maxp") return false unless maxp glyph_id < maxp.num_glyphs end |
#glyph_for_char(char_code) ⇒ SimpleGlyph, ...
Get glyph object for a Unicode character code
Uses the cmap table to map the character code to a glyph ID, then retrieves the corresponding glyph.
122 123 124 125 126 127 |
# File 'lib/fontisan/glyph_accessor.rb', line 122 def glyph_for_char(char_code) glyph_id = char_to_glyph_id(char_code) return nil unless glyph_id glyph_for_id(glyph_id) end |
#glyph_for_id(glyph_id) ⇒ SimpleGlyph, ...
Get glyph object for a glyph ID
Returns the appropriate glyph object based on the font format:
- TrueType fonts:
SimpleGlyphorCompoundGlyph - CFF fonts:
CFFGlyph
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/fontisan/glyph_accessor.rb', line 83 def glyph_for_id(glyph_id) validate_glyph_id!(glyph_id) # Check cache first and update access time if @glyph_cache.key?(glyph_id) @glyph_access_times[glyph_id] = Time.now.to_f return @glyph_cache[glyph_id] end glyph = if truetype? truetype_glyph(glyph_id) elsif cff? cff_glyph(glyph_id) else raise Fontisan::MissingTableError, "Font has neither glyf nor CFF table" end # Evict least recently used entry if cache is full evict_lru_glyph if @glyph_cache.size >= MAX_GLYPH_CACHE_SIZE @glyph_cache[glyph_id] = glyph @glyph_access_times[glyph_id] = Time.now.to_f glyph end |
#glyph_for_name(glyph_name) ⇒ SimpleGlyph, ...
Get glyph object for a PostScript glyph name
Uses the post table (if available) to map the glyph name to a glyph ID. This method is primarily useful for fonts with post table version 2.0.
143 144 145 146 147 148 |
# File 'lib/fontisan/glyph_accessor.rb', line 143 def glyph_for_name(glyph_name) glyph_id = name_to_glyph_id(glyph_name) return nil unless glyph_id glyph_for_id(glyph_id) end |
#has_glyph_for_char?(char_code) ⇒ Boolean
Check if a Unicode character is mapped in the font
284 285 286 |
# File 'lib/fontisan/glyph_accessor.rb', line 284 def has_glyph_for_char?(char_code) !char_to_glyph_id(char_code).nil? end |
#metrics_for_char(char_code) ⇒ Hash{Symbol => Integer}?
Get horizontal metrics for a Unicode character
185 186 187 188 189 190 |
# File 'lib/fontisan/glyph_accessor.rb', line 185 def metrics_for_char(char_code) glyph_id = char_to_glyph_id(char_code) return nil unless glyph_id metrics_for_id(glyph_id) end |
#metrics_for_id(glyph_id) ⇒ Hash{Symbol => Integer}?
Get horizontal metrics for a glyph ID
Returns a hash with advance width and left sidebearing in font units.
163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/fontisan/glyph_accessor.rb', line 163 def metrics_for_id(glyph_id) validate_glyph_id!(glyph_id) hmtx = font.table("hmtx") raise_missing_table!("hmtx") unless hmtx unless hmtx.parsed? # Auto-parse if not already parsed parse_hmtx_with_context!(hmtx) end hmtx.metric_for(glyph_id) end |
#outline_for_char(char) ⇒ Models::GlyphOutline?
Get outline for character
Convenience method that takes a character string and extracts its outline. The character is converted to its Unicode codepoint first.
258 259 260 261 262 263 264 265 |
# File 'lib/fontisan/glyph_accessor.rb', line 258 def outline_for_char(char) unless char.is_a?(String) && char.length == 1 raise ArgumentError, "char must be a single character String, got: #{char.inspect}" end outline_for_codepoint(char.ord) end |
#outline_for_codepoint(codepoint) ⇒ Models::GlyphOutline?
Get outline for Unicode codepoint
Maps a Unicode codepoint to a glyph ID via the cmap table, then extracts the outline for that glyph.
233 234 235 236 237 238 |
# File 'lib/fontisan/glyph_accessor.rb', line 233 def outline_for_codepoint(codepoint) glyph_id = char_to_glyph_id(codepoint) return nil unless glyph_id outline_for_id(glyph_id) end |
#outline_for_id(glyph_id) ⇒ Models::GlyphOutline?
Get outline for glyph by ID
Extracts the complete outline data for a glyph, including all contours, points, and bounding box information. The outline can be converted to SVG paths or drawing commands for rendering.
This method uses OutlineExtractor
to handle both TrueType (glyf) and CFF outline formats transparently.
For compound glyphs, it recursively resolves component dependencies.
215 216 217 218 |
# File 'lib/fontisan/glyph_accessor.rb', line 215 def outline_for_id(glyph_id) extractor = OutlineExtractor.new(@font) extractor.extract(glyph_id) end |
#truetype? ⇒ Boolean
Check if font uses TrueType outlines (glyf table)
291 292 293 |
# File 'lib/fontisan/glyph_accessor.rb', line 291 def truetype? font.table("glyf") != nil end |