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
Reference: [‘docs/ttfunk-feature-analysis.md:541-575`](docs/ttfunk-feature-analysis.md:541)
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
52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/fontisan/glyph_accessor.rb', line 52 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 = {} end |
Instance Attribute Details
#font ⇒ TrueTypeFont, OpenTypeFont (readonly)
Font instance this accessor operates on
46 47 48 |
# File 'lib/fontisan/glyph_accessor.rb', line 46 def font @font end |
Instance Method Details
#cff? ⇒ Boolean
Check if font uses CFF outlines (CFF table)
289 290 291 |
# File 'lib/fontisan/glyph_accessor.rb', line 289 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.
367 368 369 370 371 372 373 374 |
# File 'lib/fontisan/glyph_accessor.rb', line 367 def clear_cache @glyph_cache.clear @closure_cache.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.
319 320 321 322 323 324 325 326 327 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 |
# File 'lib/fontisan/glyph_accessor.rb', line 319 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
262 263 264 265 266 267 268 269 |
# File 'lib/fontisan/glyph_accessor.rb', line 262 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.
113 114 115 116 117 118 |
# File 'lib/fontisan/glyph_accessor.rb', line 113 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)
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/fontisan/glyph_accessor.rb', line 83 def glyph_for_id(glyph_id) validate_glyph_id!(glyph_id) return @glyph_cache[glyph_id] if @glyph_cache.key?(glyph_id) 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 @glyph_cache[glyph_id] = 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.
134 135 136 137 138 139 |
# File 'lib/fontisan/glyph_accessor.rb', line 134 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
275 276 277 |
# File 'lib/fontisan/glyph_accessor.rb', line 275 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
176 177 178 179 180 181 |
# File 'lib/fontisan/glyph_accessor.rb', line 176 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.
154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/fontisan/glyph_accessor.rb', line 154 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.
249 250 251 252 253 254 255 256 |
# File 'lib/fontisan/glyph_accessor.rb', line 249 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.
224 225 226 227 228 229 |
# File 'lib/fontisan/glyph_accessor.rb', line 224 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.
206 207 208 209 |
# File 'lib/fontisan/glyph_accessor.rb', line 206 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)
282 283 284 |
# File 'lib/fontisan/glyph_accessor.rb', line 282 def truetype? font.table("glyf") != nil end |