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`](lib/fontisan/glyph_accessor.rb) 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`](docs/ttfunk-feature-analysis.md:541)
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 63 64 65 66 |
# File 'lib/fontisan/glyph_accessor.rb', line 55 def initialize(font) raise ArgumentError, "Font cannot be nil" if font.nil? unless font.respond_to?(:table) raise ArgumentError, "Font must respond to :table method" end @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)
302 303 304 |
# File 'lib/fontisan/glyph_accessor.rb', line 302 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.
380 381 382 383 384 385 386 387 388 |
# File 'lib/fontisan/glyph_accessor.rb', line 380 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 if glyf.respond_to?(: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.
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 368 369 370 371 372 373 |
# File 'lib/fontisan/glyph_accessor.rb', line 332 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.respond_to?(:compound?) && glyph.compound? # Add component glyph IDs if glyph.respond_to?(:components) 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 end result end |
#glyph_exists?(glyph_id) ⇒ Boolean
Check if a glyph ID exists and is valid
275 276 277 278 279 280 281 282 |
# File 'lib/fontisan/glyph_accessor.rb', line 275 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.
126 127 128 129 130 131 |
# File 'lib/fontisan/glyph_accessor.rb', line 126 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: [‘SimpleGlyph`](lib/fontisan/tables/glyf/simple_glyph.rb) or [`CompoundGlyph`](lib/fontisan/tables/glyf/compound_glyph.rb)
-
CFF fonts: [‘CFFGlyph`](lib/fontisan/tables/cff/cff_glyph.rb)
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/fontisan/glyph_accessor.rb', line 87 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.
147 148 149 150 151 152 |
# File 'lib/fontisan/glyph_accessor.rb', line 147 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
288 289 290 |
# File 'lib/fontisan/glyph_accessor.rb', line 288 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
189 190 191 192 193 194 |
# File 'lib/fontisan/glyph_accessor.rb', line 189 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.
167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/fontisan/glyph_accessor.rb', line 167 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.
262 263 264 265 266 267 268 269 |
# File 'lib/fontisan/glyph_accessor.rb', line 262 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.
237 238 239 240 241 242 |
# File 'lib/fontisan/glyph_accessor.rb', line 237 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`](lib/fontisan/outline_extractor.rb) to handle both TrueType (glyf) and CFF outline formats transparently. For compound glyphs, it recursively resolves component dependencies.
219 220 221 222 |
# File 'lib/fontisan/glyph_accessor.rb', line 219 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)
295 296 297 |
# File 'lib/fontisan/glyph_accessor.rb', line 295 def truetype? font.table("glyf") != nil end |